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
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Shared text, 1โ10,000 characters. Each platform also applies its own limit. |
targets | array | Yes | 1โ20 accounts to publish to. |
targets[].accountId | UUID | Yes | An account ID from GET /v1/accounts. |
targets[].content | string | No | Replaces content for this account only (1โ10,000 characters). |
targets[].options | object | No | Platform-specific settings. See Platform Options. |
media | array | No | Up to 10 items, shared by every target. See Media & Uploads. |
media[].url | string | Yes | Public http(s) URL or media://<id> library reference. |
media[].type | string | Yes | image, video or document (PDF, LinkedIn only). |
media[].altText | string | No | Alt text, up to 1,000 characters, used where the platform supports it. |
media[].title | string | No | Document title (documents only), up to 400 characters. |
publishNow | boolean | One of | true publishes immediately. |
scheduledFor | string | One of | ISO 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) andpublishedAt. - A failed target has
errorCodeanderrorMessage. Codes aremedia_error,invalid_content,auth_revoked,rate_limitedandplatform_error, see Errors. - The post's
statusrolls up the targets:published,partialorfailed. 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.
Related
This page is also available as Markdown.