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
Settings → API keys → New key
For development, pick test mode so you can experiment without publishing to real channels.
Export it to your shell
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.
# 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
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.
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.
curl https://api.openpost.so/v1/analytics/posts/pst_7vQxW2 \
-H "Authorization: Bearer $OP_KEY"
{
"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
- Swap the test key for a live key.
- Test mode has no real publishing; live mode hits real social APIs.
- Monitor
post.failedwebhooks and alert your team. - Set up rate-limit awareness (respect
Retry-After). - Rotate your API key on a schedule from Settings → API keys. The old key keeps working for 24h during rotation.