Blog

· 3 min read

Schedule social media posts with an API: X, LinkedIn and Bluesky in one call

A practical guide to scheduling posts across X, LinkedIn and Bluesky programmatically, with timezones, retries and webhooks handled for you.

If you're adding scheduling to a product, like a CMS, a newsletter tool or an AI content assistant, you'll quickly find that every network handles it differently. Some have limited native scheduling, some have none, and each has its own auth, limits and failure modes.

The usual answer is to store the post, run a job at the right time and call each API. That means you own a scheduler, a retry policy, token refresh and user-facing error states.

Here's the shorter path.

The one-request version

bash
curl -X POST https://hapi.tools/v1/posts \
  -H "Authorization: Bearer $HAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: campaign-42-post-1" \
  -d '{
    "content": "Our winter release is live. Here is what is new 👇",
    "platforms": ["x", "linkedin", "bluesky"],
    "scheduledAt": "2026-12-01T09:00:00-03:00",
    "timezone": "America/Sao_Paulo"
  }'

What happens:

  1. Hapi validates the content against each platform's real limits before accepting it. If the copy is too long for Bluesky or X, you get a validation error now, not a failed post at 9am.
  2. The post is stored as scheduled for that exact moment, labelled with the timezone it was planned in.
  3. At publish time, Hapi refreshes tokens if needed, publishes to each account and records a per-account result.
  4. Your webhook endpoint gets a signed post.published or post.failed event for each outcome.

Timezones that don't bite

Always send scheduledAt as an ISO 8601 timestamp with an offset (2026-12-01T09:00:00-03:00) or in UTC (2026-12-01T12:00:00Z). A time without an offset is rejected with a validation error, so a post can never go out at the wrong hour because of a server's clock. Add an IANA timezone such as America/Sao_Paulo to record which local time the post was planned in.

Tip: let a date library work out the offset, so daylight-saving changes are handled for you. With Luxon: DateTime.fromISO("2026-12-01T09:00", { zone: "America/Sao_Paulo" }).toISO() returns 2026-12-01T09:00:00.000-03:00, which Hapi accepts as is.

Different copy per network

LinkedIn rewards longer posts, Bluesky caps at 300 characters, and X has its own limits. Use platformOptions to tailor each one in the same request:

json
"platformOptions": {
  "linkedin": { "content": "Our winter release is live.\n\nThree things our customers asked for most: ..." },
  "bluesky": { "content": "Winter release is live ❄️ hapi.tools/changelog" }
}

Know when something fails

Scheduled posts fail silently in a lot of homegrown systems. Hapi signs every webhook with HMAC-SHA256 and retries delivery with exponential backoff (up to 6 attempts):

text
hapi-signature: t=1764590400,v1=5f2b...

Verify the signature, then tell your user right away: "Your LinkedIn post didn't go out because the account was disconnected. Reconnect?"

Reschedule or cancel

Scheduled posts are ordinary resources. Fetch them, update scheduledAt, or delete them through /v1/posts/{id}. The Hapi dashboard also has a drag-and-drop calendar if your team wants to manage them by hand.

Which platforms are live

As of today, scheduling and publishing are live on X, LinkedIn, Bluesky, Telegram, Discord and Slack. Instagram, TikTok, YouTube, Threads, Facebook, Pinterest and others are available in sandbox mode: same API, validated against real platform limits, with simulated results. You can build and test your UI now, and switch over when they go live.

Try it

Create a free key and schedule your first post →

Try everything for 15 days for $5. Growth: 25 accounts, 5,000 posts a month and webhooks for $49/mo. See pricing.