Authentication
Every request carries an API key in the Authorization header:
Authorization: Bearer sosp_<your key>Mint keys in the console under Settings → API access (app.sosposting.com). A key is scoped to the business it was created in: it posts to that business's connected platforms and spends that business's token wallet. Keys are shown once at creation — store them like passwords, and revoke them anytime from the same screen.
Base URL: https://app.sosposting.com. Requests and responses are JSON. API posts cost exactly the same tokens as posts made from the dashboard — see pricing.
POST /api/v1/posts — create a post
One call fans out to every platform you target. Payload fields:
type(required) —"reel","social","product"or"blog".mediaUrl— publichttps://URL of the image or video (use the presign endpoint below if you don't host media yourself).title,caption— your copy; withuseAion, the AI turns it into platform-native captions, titles and hashtags.providers— array like["INSTAGRAM","FACEBOOK","TIKTOK"]; defaults to all eligible connected platforms for the post type.useAi— defaulttrue; setfalseto publish your text verbatim.scheduledAt— ISO 8601 timestamp to schedule instead of publishing now.
curl -X POST https://app.sosposting.com/api/v1/posts \
-H "Authorization: Bearer sosp_..." \
-H "Content-Type: application/json" \
-d '{
"type": "reel",
"mediaUrl": "https://example.com/clip.mp4",
"title": "Summer drop",
"caption": "Our new summer collection is live",
"providers": ["INSTAGRAM", "FACEBOOK", "YOUTUBE"],
"useAi": true,
"scheduledAt": "2026-08-01T09:00:00Z"
}'Returns 201 with the post id and one target per platform:
{
"id": "cmcz...",
"scheduledAt": "2026-08-01T09:00:00.000Z",
"targets": [
{ "id": "t1...", "status": "QUEUED" },
{ "id": "t2...", "status": "QUEUED" }
]
}GET /api/v1/posts/:id — status & metrics read-back
Poll the post to see how delivery went and how the post performs. Delivery status is returned for every target; engagement metrics are polled from Facebook, Instagram and YouTube only — the platforms whose APIs expose per-post insights to us. Metrics are refreshed at most once per hour, on demand:
curl https://app.sosposting.com/api/v1/posts/cmcz... \
-H "Authorization: Bearer sosp_..."{
"id": "cmcz...",
"createdAt": "2026-07-13T10:00:00.000Z",
"scheduledAt": null,
"status": "POSTED",
"targets": [
{
"provider": "INSTAGRAM",
"status": "POSTED",
"externalId": "1789...",
"externalUrl": "https://www.instagram.com/p/...",
"error": null,
"metrics": { "views": 1200, "likes": 88, "comments": 7 }
}
]
}- Post-level
statusrolls up the targets:QUEUED,SCHEDULED(waiting for its scheduled time),PROCESSING,POSTED,PARTIAL,FAILEDorSKIPPED. - TikTok targets show
PENDING_USERwhile the video sits in the account's TikTok drafts waiting for a manual publish — it isn't publicly live yet, so it has noexternalUrl. metricsis populated forFACEBOOK,INSTAGRAMandYOUTUBEtargets only; every other provider (TikTok, Pinterest, LinkedIn, Threads, Bluesky, WordPress, Shopify) always returnsmetrics: null. Even on a supported platform it isnulluntil the first refresh after delivery, and individual keys are omitted when the platform won't report them —sharescomes from Facebook only, andviewsfrom video posts.metricsAttells you when a target's metrics were last refreshed.- Unknown or foreign post ids return
404.
POST /api/v1/media/presign — upload media without your own bucket
Ask for a presigned upload, PUT the bytes, then use the returned publicUrl as mediaUrl when creating the post:
curl -X POST https://app.sosposting.com/api/v1/media/presign \
-H "Authorization: Bearer sosp_..." \
-H "Content-Type: application/json" \
-d '{
"filename": "clip.mp4",
"contentType": "video/mp4",
"sizeBytes": 10485760
}'{
"uploadUrl": "https://...r2.cloudflarestorage.com/...", // PUT the raw bytes here
"publicUrl": "https://media.../uploads/....mp4", // use as mediaUrl
"key": "uploads/....mp4"
}curl -X PUT "<uploadUrl>" \
-H "Content-Type: video/mp4" \
--data-binary @clip.mp4Limits: images up to 25 MB, videos up to 50 MB. sizeBytes is required (missing it returns 400). Unsupported content types are rejected with 415, oversize files with 413; presign requests are rate-limited per business (429).
Tokens & errors
- Same pricing as the dashboard: an API post is charged the same tokens as the identical post made in the console — see what tokens buy. Failed deliveries on our side are refunded automatically.
- Errors return a JSON body
{ "error": "...", "code": "..." }— thecodeis a stable machine-readable value (e.g.insufficient_tokens,invalid_media_url,rate_limited) — with a conventional status code:400bad request,401bad key,402not enough tokens,404not found,413/415media limits,429rate limited.
Good to know
- Connect your platforms once in the console — the API publishes through the same integrations.
- Run several brands? Each business has its own key and connected accounts, and businesses under one account share a single token wallet — no per-seat or per-workspace fees.
- The API is versioned under
/api/v1/; changes are additive.