DEV Community

Ejaz Arain
Ejaz Arain

Posted on Edited on Originally published at rutba.io AI-assisted

Publishing to several social networks with one API call, shown with a real response

First published on rutba.io.

A diagram: one post fanning out to Bluesky, Mastodon, Telegram, Discord and WordPress, with one result recorded for each account.

Rutba Relay takes one post and a list of accounts, adapts the post to each network, publishes in the background and reports a separate result for every account. This article shows a real request and response from that API, lists what a customer can publish to today, and compares it with Ayrshare and Hootsuite, whose pricing and documentation we read on 10 November 2026.

What a customer can publish to today

The catalogue at GET /v1/platforms lists 55 destinations, and Relay has an adapter for 18 of them. Five are open to any customer now, and a sixth is a test destination that reaches no network at all.

Destination How you connect it What the adapter allows
Bluesky Handle and app password 300 characters, up to 4 images of 0.9 MB
Discord Incoming webhook URL 2,000 characters, up to 10 images or videos of 10 MB
Mastodon Sign-in on your own instance 500 characters, up to 4 images, or 1 video of 40 MB
Telegram Channel name. Use our bot or your own, and make it an admin of the channel 4,096 characters, up to 10 images of 10 MB or videos of 50 MB
WordPress Self-hosted site URL, username and application password No practical text limit, up to 10 images, native tags
Sandbox Nothing to set up For test suites. No network is contacted

Read from the live catalogue on 10 November 2026.

The other twelve adapters are written. Five of those networks can be connected within the limits each one sets until it has reviewed our app, and those limits decide whether a customer can use them. The other seven cannot be connected yet.

Network State on 10 November What that means for a customer
Facebook Pages, Instagram, Threads Connectable within Meta's limits. Our app stays in Development mode until Meta's review is done Only people with a role on our app can connect, so customers cannot use these yet
TikTok Connectable within TikTok's limits while it reviews our app, on its sandbox keys Only named test accounts can connect, and posts stay private to the creator
YouTube Connectable within Google's limits. Google has verified our app's data access, and its separate YouTube API audit has not passed yet Every upload is forced private until that audit passes, so it is no use for publishing yet
LinkedIn, Pinterest, Reddit, Slack No app registered yet Cannot be connected
Google Business Profile, Snapchat Each opens this API by request, and we do not have access yet Cannot be connected
X No app registered. X charges per request, so it will be sold against prepaid credit, and that is not in place Cannot be connected

If you build a picker from the catalogue

available: true means this deployment holds the keys for that network's app. Facebook reports true before Meta's review has finished, so read integration_status as well. gated means the network's own review still applies, and note says what it restricts.

On the Agency plan an organisation can register its own app with a network through /v1/org/platform-apps, and connections then use that app in place of ours. Our test suite covers that route with an invented Facebook app. It has not been proven with a customer's approved app, so talk to us before planning around it.

The run behind the examples

The requests and responses below come from one run on 10 November 2026: the Relay gate, its delivery worker and the public API door, running on a development machine and called with an API key. Every publish went to Sandbox accounts, so no social network was contacted. The previews used the real rules for Bluesky and Mastodon, with connection rows we inserted for the run, because a preview only adapts the post and never reaches a network.

The ids are the ones that run produced, and the responses are trimmed to the fields that matter. On the hosted service the base URL is https://api.relay.rutba.io and live keys begin rsk_live_.

The free plan includes API keys and one webhook endpoint. Its keys reach the Sandbox only, so you can build against the Sandbox accounts used below without paying. The stated allowance is 500 Sandbox calls a month, and the plan's limit of 30 posts a month counts Sandbox posts too, so a test suite that publishes often will reach that first. A free key that targets a live network is refused at the request with a payment_required error naming the networks, before anything is queued.

Publishing to a live network through the API needs the Agency plan, listed at $99 a month. Agency is sold by written quote today rather than self-serve checkout, through the Rutba Social page. Once a workspace is on it, the API key works immediately.

Connect an account

curl -X POST https://api.relay.rutba.io/v1/connections \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "platform": "sandbox", "fields": { "handle": "@first-account" } }'
Enter fullscreen mode Exit fullscreen mode
{
  "data": {
    "id": "u0czr5buhlftcszrxuik6ant",
    "platform": "sandbox",
    "handle": "@first-account",
    "display_name": "Sandbox account",
    "status": "active"
  }
}
Enter fullscreen mode Exit fullscreen mode

Bluesky, Discord, Telegram and WordPress connect through this same call, each with its own fields, which the catalogue lists per network. Mastodon goes through a sign-in on your own instance. For an account that belongs to somebody else, POST /v1/connect-sessions makes a hosted link they can open themselves.

See what each network will get

A preview runs the same adaptation the publisher runs and writes nothing. This post is 372 characters long, which suits Mastodon and does not suit Bluesky.

curl -X POST https://api.relay.rutba.io/v1/posts/preview \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "content": { "text": "Version 4 of our invoicing app is out today. Recurring invoices can now be paused for a month without losing the schedule, statements export to CSV as well as PDF, and the slow customer search on accounts with more than 20,000 rows is fixed. The full list of changes is on the changelog, and the upgrade is automatic for every hosted account, so nobody has to do anything." },
    "connection_ids": ["vg8ubpzn43y38uqxpki8wr3u", "qeiby35tu44xz6t0kjjt9adf"]
  }'
Enter fullscreen mode Exit fullscreen mode

Bluesky's part of the 200 response:

{
  "connection_id": "vg8ubpzn43y38uqxpki8wr3u",
  "platform": "bluesky",
  "handle": "yourbrand.bsky.social",
  "connection_status": "active",
  "text": "Version 4 of our invoicing app is out today. Recurring invoices can now be paused for a month without losing the schedule, statements export to CSV as well as PDF, and the slow customer search on accounts with more than 20,000 rows is fixed. The full list of changes is on the changelog, and the…",
  "warnings": [
    {
      "code": "too_long",
      "field": "text",
      "message": "Bluesky allows 300 characters, got 372",
      "fixable": true
    }
  ],
  "blocking": [],
  "publishable": true
}
Enter fullscreen mode Exit fullscreen mode

The text was cut at a word boundary to 296 characters, and the warning says why. Mastodon's copy came back whole at 372 with no warnings. Send "validation": "strict" and nothing is adapted: in the same run Bluesky came back with "publishable": false and a too_long entry under blocking, and Mastodon stayed publishable.

On Bluesky, Mastodon, Telegram and Discord, Relay puts the link and hashtags in the body, so they count towards the limit. In lenient mode it shortens your words to make room and leaves the link and every hashtag whole. Given the same 372 characters with a link and two tags, the Bluesky rules cut the words to 246 characters and the post came to 297. The 192-character post in the next step comes to 243 with that link and those tags.

Publish to several accounts at once

One request, with an Idempotency-Key so that a retry cannot post twice. The third target uses a Sandbox override that asks for a refusal, so the response shows what a failure looks like.

curl -X POST https://api.relay.rutba.io/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Idempotency-Key: release-4-announcement" \
  -H "content-type: application/json" \
  -d '{
    "content": {
      "text": "Version 4 of our invoicing app is out today. Recurring invoices can now be paused for a month, statements export to CSV as well as PDF, and the slow customer search on large accounts is fixed.",
      "link": "https://example.com/changelog/4",
      "tags": ["changelog", "saas"]
    },
    "targets": [
      { "connection_id": "u0czr5buhlftcszrxuik6ant" },
      { "connection_id": "dlhdpjmr5jr8wkbpxf1vg5q3" },
      { "connection_id": "rzesyqu5iao9x44oxbodhogm",
        "overrides": { "outcome": "content_rejected" } }
    ]
  }'
Enter fullscreen mode Exit fullscreen mode

The answer is a 202, trimmed here to the post and its deliveries:

{
  "data": {
    "id": "gsvpcm1j1n4jerme5kxrmej7",
    "status": "queued",
    "source": "api",
    "deliveries": [
      { "id": "tkcmunic8f9vgbkun4lz42gt", "platform": "sandbox",
        "connection_id": "u0czr5buhlftcszrxuik6ant", "status": "pending", "attempts": 0 },
      { "id": "pmjmr51qfyj9pmzsx5xliv0x", "platform": "sandbox",
        "connection_id": "dlhdpjmr5jr8wkbpxf1vg5q3", "status": "pending", "attempts": 0 },
      { "id": "oliszvo4p9bah2ouekd75dn8", "platform": "sandbox",
        "connection_id": "rzesyqu5iao9x44oxbodhogm", "status": "pending", "attempts": 0 }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

No network has been contacted at this point. The API has written one post and one delivery per target, and each delivery is queued on its own, so the call returns quickly however many accounts it names and a slow network cannot hold up the others.

Replace targets with "platforms": ["bluesky", "mastodon", "telegram"] to reach every active account on those networks. If one of them has no active connection, the request is refused with a 409 and connection_unavailable. Add scheduled_at and timezone to send the post later.

Read the result

curl https://api.relay.rutba.io/v1/posts/gsvpcm1j1n4jerme5kxrmej7 \
  -H "Authorization: Bearer $RELAY_API_KEY"
Enter fullscreen mode Exit fullscreen mode
{
  "data": {
    "id": "gsvpcm1j1n4jerme5kxrmej7",
    "status": "partial",
    "published_at": "2026-11-10T04:29:10.419Z",
    "deliveries": [
      {
        "id": "tkcmunic8f9vgbkun4lz42gt",
        "platform": "sandbox",
        "status": "succeeded",
        "attempts": 1,
        "remote_id": "sbx_d4c45fa83f129e36b5c0f119",
        "remote_url": "https://sandbox.rutba.io/p/sbx_d4c45fa83f129e36b5c0f119",
        "error": null,
        "error_code": null
      },
      {
        "id": "pmjmr51qfyj9pmzsx5xliv0x",
        "platform": "sandbox",
        "status": "succeeded",
        "attempts": 1,
        "remote_id": "sbx_f490bd67448b7d2ddee4b8ba",
        "remote_url": "https://sandbox.rutba.io/p/sbx_f490bd67448b7d2ddee4b8ba",
        "error": null,
        "error_code": null
      },
      {
        "id": "oliszvo4p9bah2ouekd75dn8",
        "platform": "sandbox",
        "status": "rejected",
        "attempts": 1,
        "remote_id": null,
        "remote_url": null,
        "error": "Sandbox: the post was refused on request",
        "error_code": "content_rejected"
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Two accounts took the post and one refused it, so the post as a whole is partial. The refused delivery shows one attempt, because a refusal is final. Rate limits and outages are retried, each delivery on its own budget. GET /v1/deliveries?post_id=… returns the same rows with filters and a cursor, and POST /v1/deliveries/{id}/retry sends one destination again, which Relay refuses for a delivery the network has rejected.

Send it twice on purpose

A timeout leaves you not knowing whether the post went out, so the safe move is to send the same request again. With the same Idempotency-Key and body, the run returned the original post id and a response header of idempotent-replay: true. Keys are remembered for 24 hours. The same key with a different body is refused with a 409:

{
  "error": {
    "code": "idempotency_conflict",
    "message": "Idempotency-Key \"release-4-announcement\" was already used with a different request body"
  }
}
Enter fullscreen mode Exit fullscreen mode

Hear about it by webhook

This post produced five events at the test receiver: post.created, delivery.failed, delivery.succeeded twice and post.partial. Do not build on their order. In an earlier run the same day, post.partial arrived before the second delivery.succeeded.

{
  "id": "evt_cw8G-dyyQXy0ZPA2",
  "type": "post.partial",
  "created_at": "2026-11-10T04:29:10.460Z",
  "data": {
    "post_id": "gsvpcm1j1n4jerme5kxrmej7",
    "status": "partial",
    "succeeded": 2,
    "failed": 1,
    "external_id": null,
    "deliveries": [
      { "id": "tkcmunic8f9vgbkun4lz42gt", "platform": "sandbox", "status": "succeeded",
        "remote_url": "https://sandbox.rutba.io/p/sbx_d4c45fa83f129e36b5c0f119",
        "error": null, "error_code": null },
      { "id": "pmjmr51qfyj9pmzsx5xliv0x", "platform": "sandbox", "status": "succeeded",
        "remote_url": "https://sandbox.rutba.io/p/sbx_f490bd67448b7d2ddee4b8ba",
        "error": null, "error_code": null },
      { "id": "oliszvo4p9bah2ouekd75dn8", "platform": "sandbox", "status": "rejected",
        "remote_url": null,
        "error": "Sandbox: the post was refused on request", "error_code": "content_rejected" }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Each request carries x-relay-signature: t=<unix seconds>,v1=<hex>, an HMAC-SHA256 of the timestamp and the raw body joined by a full stop. A retry of the same event keeps the same x-relay-delivery id, so you can deduplicate on it.

Against Ayrshare and Hootsuite

Start with what counts against us. Ayrshare and Hootsuite already publish to Instagram, TikTok and LinkedIn for their customers, and Relay cannot yet. If one of those networks is the reason for your integration, that settles it for now.

Rutba Relay Ayrshare Hootsuite
What it is A publishing API with a web console on the same routes A publishing API for developers, with a light dashboard A social media management suite, with a REST API beside it
Lowest listed price with the API Free for the Sandbox only, up to 30 posts a month. For live networks, $99 a month (Agency), by written quote, no cap on accounts or seats $149 a month (Premium), one profile with up to 14 accounts Its plans page does not say which plans include the API. Standard starts at $99 per user a month on annual billing, with 10 accounts
Growing Agency has no account or seat cap $299 a month for up to 10 profiles, $599 for 30, then a per-profile rate Professional starts at $199 per user a month, with unlimited accounts
Trying it A free plan with the console, and API keys that reach the Sandbox only A 28-day trial on Launch, no free plan A 14-day trial
Open to customers today Bluesky, Discord, Mastodon, Telegram, WordPress Bluesky, Facebook, Google Business, Instagram, LinkedIn, Pinterest, Reddit, Snapchat, Telegram, Threads, TikTok, X, YouTube Instagram, TikTok, LinkedIn and the other large networks, plus Bluesky
The call POST /v1/posts answers 202, then one delivery per account POST /api/post returns a postIds entry per platform POST /v1/messages takes socialProfileIds and a send time at least five minutes ahead
Test destination Sandbox accounts that refuse, rate-limit or lose a response on request Parameters such as randomPost for test content. We found no separate sandbox in its docs None in the guides we read
Repeated requests Idempotency-Key, kept for 24 hours, replays the first answer Optional idempotencyKey, duplicates are rejected Not covered in its scheduling guide
Inbox and listening Neither A messaging API for Instagram, Facebook, WhatsApp and X Inbox, listening and benchmarking

Read on 10 November 2026 from ayrshare.com/pricing, ayrshare.com/docs, hootsuite.com/plans and developer.hootsuite.com. Prices in US dollars.

Our prices are marked as indicative while plans are being finalised, and a quote is confirmed in writing before anything is charged.

Which one fits

  • Hootsuite if a marketing team needs to answer comments and watch mentions from one screen. Relay has no inbox and no listening.
  • Ayrshare if your product needs Instagram, TikTok, LinkedIn or X this month, or if you post for thousands of your own customers and want a per-profile price.
  • Relay if your destinations are Bluesky, Mastodon, Telegram, Discord or WordPress, if somebody who does not write code also needs a console, or if you want your test suite to exercise refusals and rate limits on demand. Ayrshare's publish endpoint lists Bluesky and Telegram, and it does not list Mastodon, Discord or WordPress.

Every endpoint, field and plan limit is on relay.rutba.io, and the platforms page shows the state of each network. Read the API reference.

Top comments (0)