Swat.io Developer DocumentationSwat.io Developer Documentation
Home
Getting Started
  • Overview
  • Core Resources
  • Posts
  • Drafts
  • Campaigns
  • Asset Library
MCP
Home
Getting Started
  • Overview
  • Core Resources
  • Posts
  • Drafts
  • Campaigns
  • Asset Library
MCP
  • Posts

Posts

Post type compatibility

The API uses the same postCreate mutation for all channel types, but not every post type is supported on every channel. Submitting an unsupported combination will result in a validation error.

Channeltextlinkphotovideodocument
Facebook✅✅✅✅❌
Instagram❌❌✅✅❌
LinkedIn✅✅✅✅✅
X (Twitter)✅✅✅✅❌
Bluesky✅✅✅✅❌
Threads✅✅✅✅❌
YouTube❌❌❌✅❌
Pinterest❌❌✅✅❌
TikTok❌❌✅✅❌

Notes:

  • document posts (PDF) are LinkedIn-only.
  • On Facebook and Instagram, a photo post with multiple attachments is published as a carousel.
  • GIFs are submitted as category: photo.
  • YouTube Shorts are published as category: video.
  • Instagram video posts are published as Reels.
  • Swat.io can only offer what the platform's official API supports - native platform capabilities may not always be available.

For the full breakdown including Stories, Reels, and other platform-specific formats, see the Supported Post Types help article.

Available fields

A post exposes the following fields:

FieldTypeDescription
idIDUnique identifier
categoryPostCategoryPost type: text, link, photo, video, document
channel_idIDTarget channel
publication_atDateTimeISO 8601 scheduled publish time
messageStringPost text content
statusPostStatusWorkflow status: suggested, approved, posted
attachments[Attachment]Media attachments associated with this post
network_createdDateTimeISO 8601 creation timestamp on the social network
modifiedDateTimeISO 8601 last-modified timestamp

Creating Posts

Creating a post is part of the Swat.io Publisher. There are different post types, available, so pick the one needed.

In addition to the category-specific fields shown below, a few commonly useful optional fields apply to every postCreate call:

FieldTypeNotes
assigned_user_idIDDefaults to the token user; a post must always be assigned.
campaign_idIDLink the post to a campaign. See Campaigns.
tags[String!]Tags to apply on create. On update, manage tags via postsAddTag / postsRemoveTag.
titleStringVideo posts only; max 1024 bytes.
autopublishBooleanIf true, the post is auto-published once status is approved. Defaults to the channel's setting.

Text Post

mutation postCreateText {
  postCreate(
    post: {
      category: text
      channel_id: "<CHANNEL ID>"
      publication_at: "2024-04-04T14:00:00+02:00"
      message: "this is the text of the post"
      status: suggested
    }
  ) {
    id
  }
}

Link Post

Link posts treat the link as an attachment.

mutation postCreateLink {
  postCreate(
    post: {
      category: link
      status: suggested
      channel_id: <CHANNEL ID>
      publication_at: "2024-09-09T08:35:10+02:00"
      message: "a link post"
      attachments: [
        {
          category: link
          link: "https://swat.io"
        }
      ]
    }
  ) {
    id
    attachments {
      id
    }
  }
}

Photo Post

mutation postCreatePhoto {
  postCreate(
    post: {
      category: photo
      channel_id: "<CHANNEL ID>"
      publication_at: "2024-04-16T14:00:00+02:00"
      message: "Look at this photo"
      status: suggested
      attachments: [
        {
          category: photo
          source_uploaded: "<URL>" # URL from S3 upload
        }
      ]
    }
  ) {
    id
  }
}

Video post

mutation postCreateVideo {
  postCreate(
    post: {
      category: video
      channel_id: "<CHANNEL ID>"
      publication_at: "2024-11-05T14:00:00+02:00"
      message: "this is the text of the post"
      status: suggested
      attachments: {
        category: video
        source_uploaded: "<PUBLIC_URL_TO_VIDEO>"
        source_alternate_uploaded: "<OPTIONAL_PUBLIC_URL_TO_VIDEO_THUMBNAIL>"
      }
    }
  ) {
    id
    attachments {
      id
    }
  }
}

Document post (PDF) for LinkedIn

mutation postCreateDocument {
  postCreate(
    post: {
      category: document
      channel_id: "<CHANNEL ID>"
      publication_at: "2024-04-16T14:00:00+02:00"
      message: "this is the text of the post"
      status: suggested
      attachments: {
        category: document
        title: "This is a PDF document"
        source_uploaded: "<PUBLIC_URL_TO_PDF>"
      }
    }
  ) {
    id
    attachments {
      id
    }
  }
}

Updating Posts

The same requirements regarding the post fields as outlined in Creating Posts apply. Additionally: the attachments.category must also be sent.

Tips

When providing the attachments field, all attachments must be provided. Attachments missing will be removed, superfluous attachments will be added. See Handling Attachments below.

mutation postUpdatePhoto {
  postUpdate(
    id: <POST_ID>
    post: {
      id: <POST_ID>
      channel_id: <CHANNEL_ID>
      publication_at: "2024-04-16T14:00:00+02:00"
      attachments: [
        {
          category: photo
          source_uploaded: "<PUBLIC_URL_TO_IMAGE>"
        }
      ]
    }
  ) {
    id
    attachments {
      id
      source_uploaded
    }
  }
}

Tags on update

The tags field is only available on postCreate. To add or remove tags on an existing post, use postsAddTag / postsRemoveTag (see Core Resources).

Simple updates (status, assignee, publication time)

For straightforward changes - reassigning, re-scheduling, or flipping the status - postUpdate also accepts those fields at the top level, without the post: { … } input. The two modes cannot be combined.

mutation quickUpdate {
  postUpdate(
    id: <POST_ID>
    status: approved
    assigned_user_id: <USER_ID>
    publication_at: "2024-04-16T14:00:00+02:00"
  ) {
    id
    status
  }
}

Duplicating a post

To duplicate a post and its assets to another channel, we provide the postDuplicate mutation with these minimum required fields:

mutation postDuplicate {
  postDuplicate(
    # Required args
    id: <POST_ID>
    channel_id: <CHANNEL_ID>
    assigned_user_id: <USER_ID>
    publication_at: "2026-03-03T15:00:00"
    # Optional args
    status: approved
  ) {
    id
    attachments {
      id
    }
  }
}

In addition to take care of also duplicating the assets, another benefit is that internally Swatio “relates” the duplicated posts to each other, which then can be seen in the Publisher.

The assets returned, and their URL they’re pointing too, will for a brief moment still reference the original ones from the original post, as the procedure to duplicate them and update the attachments happens in the background. After a brief moment (depending on the asset size), a re-fetch of the assets will show that the URLs have changed.

Compatibility between channel types

Duplicating is supported to any existing channel type.

However, due to the capabilities of individual channel types, it’s not possible in all cases to preserve all the attributes of a post or its asset. As an extreme example: when you duplicate a Facebook photo posts to a YouTube channel, the asset are in fact not duplicated but ignored: the post on the YouTube channel will have no assets attached, hence it also won’t be possible.

By default, duplicated posts are moved into status=suggested. It is possible to explicitly provide status: approved for the mutation, but if the system detects a necessary change (see above: loss of required media or other attributes of a post not making it eligible for publishing, etc.) the status of the post will be reverted back to suggested. In such a case it’s recommended to verify the returned posts status to detect this.

Handling Attachments

There are no dedicated endpoints to create/update/delete attachments, as they’re only relevant in the context of a post and therefore are part of the postCreate and postUpdate APIs.

If you want to work with the Library, please refer the the Asset Library Section.

That is, in both cases the arguments for the attachments can be passed to mutation. The minimum required fields are category and source_uploaded. Depending on the type of attachment, other fields might be relevant too (e.g. source_alternate_uploaded for Thumbnails of videos, iff supported by the respective social media network).

The field source_uploaded must be a publicly available URL of the resource. Once the attachment is created in Swat.io, with a short delay a background job will upload those attachments to Swat.ios own AWS S3 bucket storage. Therefore these URLs will change at least once.

Especially when updating a post, the absence & presence of the attachments fields and their contents will determine if attachments are created, updated, or deleted.

When providing attachments again (for re-ordering or updating non-URL information, like link text etc.) please make sure to fetch the post attachments after they’ve been first created so the Swat.io specific AWS S3 bucket URL is provided again. Otherwise, the system will detect the field source_uploaded as changed and will re-upload the attachment every time it is changed

Examples:

  • Assume we have a post with a single attachment

    mutation postCreatePhoto {
      postCreate(
        post: {
          category: photo
          channel_id: "<CHANNEL ID>"
          publication_at: "2024-04-16T14:00:00+02:00"
          message: "this is the text of the post"
          status: suggested
          attachments: {
            category: photo
            source_uploaded: "<PUBLIC_URL_TO_IMAGE>"
          }
        }
      ) {
        id
        attachments {
          id
        }
      }
    }
    
  • To delete all attachments, provide an empty array:

    mutation postUpdatePhoto {
      postUpdate(
        id: <POST_ID>
        post: {
          id: <POST_ID>
          channel_id: <CHANNEL_ID>
          publication_at: "2024-04-16T14:00:00+02:00"
          attachments: []
        }
      ) {
        id
        attachments {
          id
          source_uploaded
        }
      }
    }
    

Uploading assets for attachments to our S3 bucket

Attachments provided to our postCreate or postUpdate mutations pointing to external resources are automatically fetched and stored in our bucket. This requires that the provided URL is publicly reachable.

Should this not be possible, assets can be directly uploaded to our bucket using Browser-Based Upload using HTTP POST by requesting a pre-signed HTTP POST request URL and it’s form fields using the special mutation attachmentRegisterS3Upload . The upload size is limited to 500MiB.

This endpoint requires the content type / mime type provided (the the final upload URLs file extension will be based on this) and a post ID for which the attachments is designated. The post has to exist already in swatio (e.g. via the postCreate endpoint) and the returned URL (url_final, after it’s uploaded) can be used for the postUpdate attachments endpoint:

mutation {
  attachmentRegisterS3Upload(
    content_type: "content / mime type" # image/jpeg , image/png, video/mp4, etc.
    post_id: <post ID>
  ) {
    curl_command
    url_upload
    url_final
    parameters {
      acl
      cache_control
      content_disposition
      content_type
      key
      policy
      x_amz_algorithm
      x_amz_credential
      x_amz_date
      x_amz_signature
    }
  }
}

This creates a pre-signed POST request for our S3 bucket, which is valid for 1 hour to upload the attachments binary data (image, video, etc.).

This command returns:

  • the url_upload, the URL to send the POST request to
  • that URL accepts a “multipart/form-data” request and requires all the parameters being sent with the form

::: hint

Everywhere the parameters have an underscore (_) in their name, it needs to be replaced with (-)

:::

To ease testing, a curl_command field is provided, which can also be used “as is” for testing. We do not recommend using this for production, but rather as a debug tool to assist in case the custom implementation has issues. Here’s a nicer formatted version on how what the curl command does:

curl \
 -X POST https://posts-swat-io.s3.eu-central-1.amazonaws.com \
 -F Cache-Control=max-age=31536000 \
 -F Content-Disposition=attachment \
 -F Content-Type=image/jpeg \
 -F acl=public-read \
 -F key=uploads/e27f4ae6f5d94d583ae1d69443738bad51006a8aeb7c05ac8bf021e93d95d80d.jpg \
 -F policy=… \
 -F x-amz-algorithm=AWS4-HMAC-SHA256 \
 -F x-amz-credential=…/…/eu-central-1/s3/aws4_request \
 -F x-amz-date=… \
 -F x-amz-signature=… \
 -F file=@<path to your local file>

Please replace @file with the path to your local file, e.g. -F file=@assets/images/file.jpg.

Warning

Do not re-use the same uploaded assets with different posts: only ever use on asset with one post! If you need to need the same asset multiple times, please re-upload it again.

Fetching posts

Fetch a single post

query post {
  post(id: <POST_ID>) {
    id
    category
    publication_at
    message
    status
    attachments {
      id
      source_uploaded
    }
  }
}

Fetch posts by IDs

This allows efficient fetching of known posts by their IDs:

  • Maximum allowed post_ids: 50
  • All posts must be from the same workspace (otherwise the whole query aborts with an error)
  • posts not found or with insufficient permissions are simply not returned in the result
query calendarPosts {
  calendarPosts(workspace_id: <WORKSPACE_ID>, post_ids: [<POST_ID_1>, <POST_ID_2>, …]) {
    id
    category
    publication_at
    message
    status
    attachments {
      id
      source_uploaded
    }
  }
}

Fetch posts from a channel using the postList query

This allows to iterate / synchronize posts in the publisher.

  • the query returns up to 50 posts per call (can be lowered via the optional limit arg; max 50)
  • it provides a pagination token for the next page

Query example:

query list {
  postList(
    workspace_id: <WORKSPACE_ID>
    channel_ids: [CHANNEL_ID]
    order: asc # default `desc`
    date_from: …
    date_to: …
  ) {
    posts {
      id
      publication_at
    }
    pagination {
      after
    }
  }
}

To fetch the next posts, re-use the pagination.after token and feed it into the next call:

query list {
  postList(
    workspace_id: <WORKSPACE_ID>
    channel_ids: [CHANNEL_ID]
    order: asc # default `desc`
    date_from: …
    date_to: …
    after: "<AFTER_TOKEN>"
  ) {
    posts {
      id
      publication_at
    }
    pagination {
      after
    }
  }
}

For pagination, it is important provide the exact same arguments to the query which were used before to retrieve the after pagination token!

Filtering by post status

To retrieve only posts with specific statuses, pass the optional post_statuses argument. Omitting it returns posts of all statuses.

query listPosted {
  postList(
    workspace_id: <WORKSPACE_ID>
    channel_ids: [<CHANNEL_ID>]
    post_statuses: [posted, approved]
  ) {
    posts {
      id
      publication_at
      status
    }
    pagination {
      after
    }
  }
}

Tips

post_statuses accepts one or more of suggested, approved, posted. When absent, posts of all statuses are returned.

Changing post status

A post's status can be updated independently using postUpdate. The available statuses are suggested, approved, and rejected.

mutation approvePost {
  postUpdate(
    id: <POST_ID>
    post: {
      id: <POST_ID>
      channel_id: <CHANNEL_ID>
      publication_at: "2024-04-16T14:00:00+02:00"
      status: approved
    }
  ) {
    id
    status
  }
}

Deleting a post

mutation deletePost {
  postDelete(id: <POST_ID>)
}

Warning

Deleted posts cannot be recovered. Make sure you are targeting the correct post ID before calling this mutation.