Publishing

Posts

Create a post for one or many accounts, customise it per account, and read back per-platform results.

A post is one request that publishes to up to 20 accounts at once. You create it with POST /v1/posts and read its results with GET /v1/posts/{id}.

Create a post

curl -X POST https://social-publish-api.onrender.com/v1/posts \
  -H "x-access-key: $RELAYCAST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7d0c3f0e-launch-post" \
  -d '{
    "content": "We just shipped dark mode ๐ŸŒ™",
    "targets": [
      { "accountId": "8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b" },
      {
        "accountId": "2b7d9e1f-3a4c-4d5e-8f6a-7b8c9d0e1f2a",
        "content": "Dark mode is here. Read the full changelog on our blog."
      }
    ],
    "media": [{ "url": "https://example.com/dark-mode.png", "type": "image", "altText": "App in dark mode" }],
    "publishNow": true
  }'
const res = await fetch("https://social-publish-api.onrender.com/v1/posts", {
  method: "POST",
  headers: {
    "x-access-key": process.env.RELAYCAST_API_KEY!,
    "Content-Type": "application/json",
    "Idempotency-Key": "7d0c3f0e-launch-post",
  },
  body: JSON.stringify({
    content: "We just shipped dark mode ๐ŸŒ™",
    targets: [
      { accountId: "8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b" },
      {
        accountId: "2b7d9e1f-3a4c-4d5e-8f6a-7b8c9d0e1f2a",
        content: "Dark mode is here. Read the full changelog on our blog.",
      },
    ],
    media: [{ url: "https://example.com/dark-mode.png", type: "image", altText: "App in dark mode" }],
    publishNow: true,
  }),
});
if (!res.ok) throw new Error(JSON.stringify(await res.json()));
const { data: post } = await res.json();
res = requests.post(
    "https://social-publish-api.onrender.com/v1/posts",
    headers={
        "x-access-key": os.environ["RELAYCAST_API_KEY"],
        "Idempotency-Key": "7d0c3f0e-launch-post",
    },
    json={
        "content": "We just shipped dark mode ๐ŸŒ™",
        "targets": [
            {"accountId": "8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b"},
            {
                "accountId": "2b7d9e1f-3a4c-4d5e-8f6a-7b8c9d0e1f2a",
                "content": "Dark mode is here. Read the full changelog on our blog.",
            },
        ],
        "media": [{"url": "https://example.com/dark-mode.png", "type": "image", "altText": "App in dark mode"}],
        "publishNow": True,
    },
)
res.raise_for_status()
post = res.json()["data"]

Request body

FieldTypeRequiredDescription
contentstringYesShared text, 1โ€“10,000 characters. Each platform also applies its own limit.
targetsarrayYes1โ€“20 accounts to publish to.
targets[].accountIdUUIDYesAn account ID from GET /v1/accounts.
targets[].contentstringNoReplaces content for this account only (1โ€“10,000 characters).
targets[].optionsobjectNoPlatform-specific settings. See Platform Options.
mediaarrayNoUp to 10 items, shared by every target. See Media & Uploads.
media[].urlstringYesPublic http(s) URL or media://<id> library reference.
media[].typestringYesimage, video or document (PDF, LinkedIn only).
media[].altTextstringNoAlt text, up to 1,000 characters, used where the platform supports it.
media[].titlestringNoDocument title (documents only), up to 400 characters.
publishNowbooleanOne oftrue publishes immediately.
scheduledForstringOne ofISO 8601 time with offset, e.g. 2026-10-12T09:00:00Z. See Scheduling.

You must send either publishNow: true or scheduledFor. If you send both, publishNow wins.

Validation

Relaycast validates the whole post for every target before anything is saved: character limits, media counts and types, required options. If anything fails, nothing is created or charged, and you get 400 with the problems grouped by account ID:

{
  "error": {
    "code": "validation_error",
    "message": "Content is not valid for some platforms",
    "details": {
      "2b7d9e1f-3a4c-4d5e-8f6a-7b8c9d0e1f2a": [
        { "field": "content", "message": "Bluesky allows 300 characters (this post has 342)" }
      ]
    }
  }
}

Fix the content (or use targets[].content to shorten it for that account) and send again. Other request errors such as unknown_account and account_inactive are listed in Errors.

The dashboard Playground runs the same validation live as you type, without creating or charging anything.

The response

A new post returns 201 Created:

{
  "data": {
    "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "content": "We just shipped dark mode ๐ŸŒ™",
    "media": [{ "url": "https://example.com/dark-mode.png", "type": "image", "altText": "App in dark mode" }],
    "status": "publishing",
    "scheduledFor": "2026-10-05T14:03:11.000Z",
    "request": { "...": "the request body as submitted" },
    "createdAt": "2026-10-05T14:03:11.000Z",
    "updatedAt": "2026-10-05T14:03:11.000Z",
    "targets": [
      {
        "id": "0a1b2c3d-...",
        "accountId": "8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b",
        "platform": "linkedin",
        "status": "queued",
        "content": null,
        "options": {},
        "attempts": 0,
        "externalPostId": null,
        "externalUrl": null,
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": null,
        "createdAt": "2026-10-05T14:03:11.000Z",
        "updatedAt": "2026-10-05T14:03:11.000Z"
      }
    ]
  }
}

A target's content is null when it uses the shared content. A replay with the same Idempotency-Key returns 200 with the original post, see Idempotency & Retries.

Get results

Publishing happens in the background. Fetch the post to see each target's outcome:

curl https://social-publish-api.onrender.com/v1/posts/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9 \
  -H "x-access-key: $RELAYCAST_API_KEY"
  • A published target has externalPostId, externalUrl (a link to the live post) and publishedAt.
  • A failed target has errorCode and errorMessage. Codes are media_error, invalid_content, auth_revoked, rate_limited and platform_error, see Errors.
  • The post's status rolls up the targets: published, partial or failed. See Core Concepts.

Unknown post IDs, or posts from another project, return 404 not_found.

List posts

GET /v1/posts returns your most recent posts, newest first, with their targets. Use limit (1โ€“100, default 20):

curl "https://social-publish-api.onrender.com/v1/posts?limit=50" -H "x-access-key: $RELAYCAST_API_KEY"

There's no cursor pagination, so for a full history use the Posts page in the dashboard, which supports search, filters and a calendar view.

Editing and deleting

The public API doesn't have endpoints to edit, cancel or delete a post. To cancel a post that hasn't published yet, delete it from the Posts page in the dashboard: pending targets are cancelled and their credits refunded. Targets that already published stay live on the platform.

This page is also available as Markdown.