Guides

Publish programmatically

Use the API in any language

Everything in the OpenPost dashboard is available through the REST API. This guide is the complete end-to-end path — you’ll create an API key, upload a piece of media, create a scheduled post, and read back its metrics.

1. Create an API key

1

Settings → API keys → New key

For development, pick test mode so you can experiment without publishing to real channels.

2

Export it to your shell

bash
export OP_KEY="op_test_sk_4kE...Zq"

2. List connected channels

Every post targets one or more integration IDs, so you need to know what’s available. Test-mode workspaces come with a few synthetic integrations pre-created.

curl https://api.openpost.so/v1/integrations \
  -H "Authorization: Bearer $OP_KEY"

3. Upload media

Two steps: ask for a pre-signed URL, then PUT the bytes directly to S3.

bash
# Step A — request upload
curl -X POST https://api.openpost.so/v1/media/upload \
  -H "Authorization: Bearer $OP_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "filename": "launch.jpg", "content_type": "image/jpeg", "size": 2148304 }'
# → { id: "med_m0kRvP", upload_url: "https://...s3...", ... }
 
# Step B — PUT the bytes
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  --data-binary @launch.jpg
In Node, the official SDK handles this with one call: op.media.upload(file). No manual pre-signed URL wrangling.

4. Create a scheduled post

curl -X POST https://api.openpost.so/v1/posts \
  -H "Authorization: Bearer $OP_KEY" \
  -H "Idempotency-Key: demo-launch-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Big launch today 🚀",
    "integrations": ["int_x_abc"],
    "media": ["med_m0kRvP"],
    "schedule_at": "2026-05-01T14:00:00Z"
  }'

5. Handle failures idempotently

Always send an Idempotency-Keyheader on POSTs. If your request fails halfway (network timeout, server error), retry with the same key — the server will return the original response if the first call actually landed, preventing duplicates.

ts
async function safeCreate(body: PostBody, key: string) {
  for (let attempt = 1; attempt <= 4; attempt++) {
    try {
      const res = await fetch("https://api.openpost.so/v1/posts", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.OP_KEY}`,
          "Idempotency-Key": key,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(body),
      });
      if (res.status >= 500) throw new Error("server");
      return await res.json();
    } catch (e) {
      if (attempt === 4) throw e;
      await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
    }
  }
}

6. Read the metrics

24 hours after the scheduled time, query analytics.

bash
curl https://api.openpost.so/v1/analytics/posts/pst_7vQxW2 \
  -H "Authorization: Bearer $OP_KEY"
json
{
  "post_id": "pst_7vQxW2",
  "totals": { "impressions": 14832, "engagement": 412, "link_clicks": 89 },
  "by_channel": {
    "x": { "impressions": 14832, "engagement": 412, "link_clicks": 89 }
  }
}

7. Subscribe to webhook events

Polling /posts/{id} works, but webhooks are cleaner. Register an endpoint via POST /v1/webhooks and handle post.published, post.failed, and integration.disconnected events. See the webhooks reference.

Ship it to production

  1. Swap the test key for a live key.
  2. Test mode has no real publishing; live mode hits real social APIs.
  3. Monitor post.failed webhooks and alert your team.
  4. Set up rate-limit awareness (respect Retry-After).
  5. Rotate your API key on a schedule from Settings → API keys. The old key keeps working for 24h during rotation.
In production, the API’s behavior is the same as test mode except that your connected social accounts actually see posts. Start with scheduled posts for a future time so you can cancel if the payload isn’t right.
Last updated April 2026 Edit this page