WWaSparks Social Developers

Guides

Publish posts

Upload media, create a post for one or more accounts, schedule it, and follow each target to its permalink.

The sequence

  1. Add media if the post has any: register it, upload the bytes, then complete it.
  2. Create the post with the accounts it goes to, the text and the media.
  3. Follow the result. Each account is a target with its own status and permalink.

Check each account's capabilities.publish first. Instagram posts need media.

1. Add media

Register an image, video or PDF with POST /v1/media. There are two ways, and you send exactly one:

# 1. register
curl -X POST https://connect.social.wasparks.com/v1/media \
  -H "Authorization: Bearer $WASPARKS_KEY" -H "X-Tenant-Id: acme-001" \
  -H "Content-Type: application/json" \
  -d '{"upload": true, "mime_type": "image/jpeg", "size_bytes": 482113}'

# 2. PUT the bytes to upload_url, with the same Content-Type
curl -X PUT "$UPLOAD_URL" -H "Content-Type: image/jpeg" --data-binary @latte.jpg

# 3. complete
curl -X POST https://connect.social.wasparks.com/v1/media/$MEDIA_ID/complete \
  -H "Authorization: Bearer $WASPARKS_KEY" -H "X-Tenant-Id: acme-001"

The response to POST /v1/media is always { media, upload_url, upload_expires_at }. With source_url the two upload fields are null.

A completed media item has a status (UPLOADING, READY or FAILED) and platform_checks, which tells you per platform whether the file will pass and why not:

json
"platform_checks": {
  "facebook_page": { "ok": true, "problems": [] },
  "instagram": { "ok": false, "problems": ["aspect_ratio"] }
}

Using media that is not READY in a post gives 400 media_not_ready.

2. Create the post

curl -X POST https://connect.social.wasparks.com/v1/posts \
  -H "Authorization: Bearer $WASPARKS_KEY" -H "X-Tenant-Id: acme-001" \
  -H "Idempotency-Key: 6d3f1c0e-1b8a-4f0a-9a56-2f0c1d7a9e11" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Pumpkin spice season is back. Come say hi!",
    "media_ids": ["0192a7c2-0e00-7d44-8b55-6c7d8e9fa001"],
    "account_ids": ["0192a7c1-6a10-7b22-8e31-4c5d6e7f8a01"]
  }'

The fields:

Field Meaning
account_ids The accounts to publish to. One target is created per account.
text The post text. Required on every platform except Instagram media posts. Length limits are per platform.
media_ids READY media to attach.
link_url, link_preview A link to share; see below.
scheduled_at Publish at this time (UTC). Leave it out to publish now.
draft true saves the post without publishing it.
overrides Per-account changes, keyed by account id: { "<account_id>": { "text": "…" } }.
options Platform options: instagram.type (FEED, REEL, STORY, CAROUSEL), facebook.type (FEED, REEL), linkedin.visibility (PUBLIC, CONNECTIONS) and linkedin.reshare_of (a post URN to reshare).
workspace_id Defaults to the org's default workspace.

Every platform's rules are checked before anything is queued. A problem comes back as 400 validation_failed, with one entry in details.fields for each account that has one:

json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are not valid.",
    "request_id": "6b1f0c2e-3d4a-4b5c-8d9e-0f1a2b3c4d5e",
    "details": { "fields": [{ "field": "account_ids[0]", "code": "media_required", "message": "Instagram posts need at least one image or video.", "account_id": "0192a7c1-6a10-7b22-8e31-4c5d6e7f8a01" }] }
  }
}

Things that are allowed but worth a look come back on the post as warnings, with the same shape.

Scheduling and drafts

3. Follow the result

Publishing is asynchronous. The create call returns the post with its targets; read it again, or listen for events, to see the outcome.

GET /v1/posts/{postId}
json
{
  "id": "0192a7c2-1f00-7c33-9a44-5b6c7d8e9f01",
  "status": "PUBLISHED",
  "targets": [
    {
      "id": "0192a7c2-1f00-7c33-9a44-5b6c7d8e9f11",
      "account_id": "0192a7c1-6a10-7b22-8e31-4c5d6e7f8a01",
      "platform": "FACEBOOK_PAGE",
      "status": "PUBLISHED",
      "permalink": "https://www.facebook.com/104857600123456/posts/998877665544332",
      "published_at": "2026-11-02T09:00:04Z",
      "error": null
    }
  ]
}

A target's status is PENDING, PUBLISHING, PUBLISHED or FAILED. A FAILED target has an error with a code and a message. The post's status follows its targets: PUBLISHED when all are published, PARTIAL when some are, FAILED when none are.

For events instead of polling, subscribe to post.published, post.partially_published, post.failed, post.target.published and post.target.failed. See Webhooks.

To try a failed target again, call POST /v1/posts/{postId}/targets/{targetId}/retry. If it failed with publish_outcome_unknown, it may in fact have been published, and a retry can publish it twice. Check the account on the platform first.

Idempotency

Send an Idempotency-Key header on POST /v1/posts. If your request times out and you repeat it with the same key, you get the original response back instead of a second post. Keys are scoped to the org and remembered for 24 hours. Reusing a key with a different request body gives 409 idempotency_conflict. More in Errors, limits and idempotency.

Delete a post

DELETE /v1/posts/{postId}?remote=true

The post is always removed on our side. With remote=true we also try to delete each published target on its platform, and report each result:

json
{
  "id": "0192a7c2-1f00-7c33-9a44-5b6c7d8e9f01",
  "status": "PUBLISHED",
  "remote_results": [
    { "target_id": "0192a7c2-1f00-7c33-9a44-5b6c7d8e9f11", "account_id": "0192a7c1-6a10-7b22-8e31-4c5d6e7f8a01", "platform": "FACEBOOK_PAGE", "status": "DELETED", "code": null, "message": null }
  ]
}

Next: Comments and inbox