Doc

Publish posts

Use the Social Service HTTP API to publish content to social platform channels

This guide is for developers who want to use the HTTP API to publish content immediately to one or more social platform channels.

v1 only supports direct publishing. Scheduled publishing is not supported. Publish targets must include channelId; sending only accountId to infer a default channel is not supported.

Endpoint

POST /api/social/v1/post

Production URL:

https://sogrowly.com/api/social/v1/post

Authentication

Requests require a Bearer Social API key. The API key must include the social:publish permission:

Authorization: Bearer sma_live_xxx
Content-Type: application/json

Request Body

{
  "workspaceId": "sws_123",
  "draft": {
    "text": "Post text",
    "assets": [
      {
        "type": "image",
        "source": "https://example.com/image.png"
      }
    ]
  },
  "targets": [
    {
      "channelId": "sch_123"
    }
  ]
}

Fields:

workspaceId        Required. Target workspace id.
draft              Required. Content draft to publish.
draft.title        Optional. Some platforms can use it as the title or text fallback.
draft.text         Optional. Body text.
draft.assets       Optional. Media asset array.
targets            Required. At least one publish target.
targets.channelId  Required. The channels[].id value from /api/social/v1/account.
targets.settings   Optional. Platform-specific publish settings. See Platform settings for supported fields.

Asset fields:

type       Required. "image" | "video" | "document"
source     Required. Remote URL or base64 data URL with a MIME prefix.
mimeType   Optional override. If omitted, it is inferred from the data URL MIME prefix or remote URL suffix. If inference fails, a default is selected by type.
order      Optional. Sort order number. If omitted, the assets array order is used: 0, 1, 2...
metadata   Optional. Additional metadata.

source supports two formats:

https://example.com/image.png
data:image/png;base64,iVBORw0KGgo...

Raw base64 is not supported. Base64 media must include the full MIME prefix, for example data:image/png;base64,....

Request Body Size Limit

When sending uploaded file content in the request body, make sure the full request body is no larger than 4 MB.

For media assets, using a publicly accessible URL as source is recommended:

https://example_public_url.jpeg

For local files, first create a presigned upload URL with /api/social/v1/media/upload, upload the file bytes, then use the returned temporaryUrl.url or publishAsset.source as the asset source.

For platform-specific targets[].settings fields, see Platform settings.

Publish Text to X

Request body:

{
  "workspaceId": "sws_123",
  "draft": {
    "text": "Hello from my integration."
  },
  "targets": [
    {
      "channelId": "sch_x_123"
    }
  ]
}

Example:

curl https://sogrowly.com/api/social/v1/post \
  -H "Authorization: Bearer sma_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "workspaceId": "sws_123",
    "draft": {
      "text": "Hello from my integration."
    },
    "targets": [
      {
        "channelId": "sch_x_123"
      }
    ]
  }'

Successful response:

{
  "status": "success",
  "statusCode": 200,
  "data": {
    "postId": "sdraft_123",
    "summary": {
      "published": 1,
      "failed": 0
    },
    "taskResults": [
      {
        "taskId": "manual_task_123",
        "platform": "x",
        "channelId": "sch_x_123",
        "status": "published",
        "providerPostId": "provider_post_123",
        "providerPostUrl": "https://x.com/example/status/123",
        "publishedAt": "1760000000000"
      }
    ]
  },
  "message": "Post published to 1 target.",
  "meta": {
    "version": "v1"
  }
}

Publish an Image to Multiple Channels

Request body:

{
  "workspaceId": "sws_123",
  "draft": {
    "text": "New product image.",
    "assets": [
      {
        "type": "image",
        "source": "https://example.com/product.png"
      }
    ]
  },
  "targets": [
    {
      "channelId": "sch_x_123"
    },
    {
      "channelId": "sch_instagram_123"
    }
  ]
}

If publishing fails for some channels, the response is still HTTP 200, but status is warning:

{
  "status": "warning",
  "statusCode": 200,
  "data": {
    "postId": "sdraft_123",
    "summary": {
      "published": 1,
      "failed": 1
    },
    "taskResults": [
      {
        "taskId": "manual_task_123",
        "platform": "x",
        "channelId": "sch_x_123",
        "status": "published",
        "providerPostId": "provider_post_123",
        "providerPostUrl": "https://x.com/example/status/123",
        "publishedAt": "1760000000000"
      },
      {
        "taskId": "manual_task_456",
        "platform": "instagram",
        "channelId": "sch_instagram_123",
        "status": "failed",
        "error": {
          "code": "validation_failed",
          "message": "Instagram publish requires at least one image or video."
        }
      }
    ]
  },
  "message": "Post published with 1 failed target.",
  "meta": {
    "version": "v1"
  }
}

Callers should read data.taskResults to determine the publish result for each target.

If all targets fail, the response uses status: "failed" and returns a non-200 HTTP status code based on the main failure reason. For example, request parameter issues usually return 400, while authentication or authorization issues return 401 or 403.

Publish a Video to YouTube

Request body:

{
  "workspaceId": "sws_123",
  "draft": {
    "title": "Product demo",
    "text": "A short product demo video.",
    "assets": [
      {
        "type": "video",
        "source": "https://example.com/demo.mp4"
      }
    ]
  },
  "targets": [
    {
      "channelId": "sch_youtube_123"
    }
  ]
}

The successful response structure is the same as above, and taskResults[].platform is "youtube".

Common Errors

Missing Authorization:

{
  "status": "failed",
  "statusCode": 401,
  "data": {},
  "message": "Authorization bearer API key is required.",
  "error": {
    "code": "invalid_auth",
    "message": "Authorization bearer API key is required."
  },
  "meta": {
    "version": "v1"
  }
}

Missing channelId:

{
  "status": "failed",
  "statusCode": 400,
  "data": {},
  "message": "Request body is invalid.",
  "error": {
    "code": "invalid_payload",
    "message": "Request body is invalid.",
    "details": {
      "issues": [
        {
          "code": "invalid_type",
          "path": "targets.0.channelId",
          "message": "Invalid input: expected string, received undefined"
        }
      ]
    }
  },
  "meta": {
    "version": "v1"
  }
}

API key missing the publish permission:

{
  "status": "failed",
  "statusCode": 403,
  "data": {},
  "message": "API key requires \"social:publish\" permission.",
  "error": {
    "code": "workspace_permission_denied",
    "message": "API key requires \"social:publish\" permission."
  },
  "meta": {
    "version": "v1"
  }
}

Channel does not belong to the workspace:

{
  "status": "failed",
  "statusCode": 400,
  "data": {},
  "message": "Publish target \"sch_123\" does not belong to workspace \"sws_123\".",
  "error": {
    "code": "workspace_mismatch",
    "message": "Publish target \"sch_123\" does not belong to workspace \"sws_123\"."
  },
  "meta": {
    "version": "v1"
  }
}

Status Codes and Rate Limits

200 -> success or warning
400 -> Invalid request body, invalid parameters, or channel mismatch
401 -> API key is missing, malformed, invalid, or expired
403 -> API key permission is insufficient, or workspace permission is insufficient
404 -> Workspace/channel does not exist
429 -> Rate limited by userId
500 -> Configuration error or unknown error

Current rate limit:

Each resolved userId can make up to 15 requests per minute.
The account and post endpoints share this rate limit.