Skip to main content

Publish via the Public API

This guide walks you through publishing content to social media platforms using the Soku Public API. By the end, you'll know how to create text posts, image posts, video posts, and scheduled posts.

Quick links: Public API Reference | Rate Limiting | API Keys


Before you start

You need three things:

  1. A Soku account with an active subscription
  2. An API key — create one in the web app at Settings > API Keys
  3. At least one connected social platform — connect via the web app Integrations page

Set up your environment:

export SOKU_API_KEY="sk_live_your_key_here"
export SOKU_BASE_URL="https://<region>-<project>.cloudfunctions.net/api"

Step 1: Post text to a single platform

The simplest possible post — text to X (Twitter):

curl -X POST "$SOKU_BASE_URL/v1/posts" \
-H "Content-Type: application/json" \
-H "soku-api-key: $SOKU_API_KEY" \
-d '{
"post": {
"content": {
"text": "My first post via the Soku API!",
"platform": "x"
}
}
}'

Response:

{ "postSubmissionId": "550e8400-e29b-41d4-a716-446655440000" }

Save the postSubmissionId — you can use it to track the post's progress in Firestore at postSubmissions/{id}.


Step 2: Post to multiple platforms at once

Pass an array of platforms to publish everywhere simultaneously:

curl -X POST "$SOKU_BASE_URL/v1/posts" \
-H "Content-Type: application/json" \
-H "soku-api-key: $SOKU_API_KEY" \
-d '{
"post": {
"content": {
"text": "Shipping update via Soku",
"platform": [
{ "platform": "x", "accountId": "x_primary" },
"threads"
]
}
}
}'

Platform targeting rules:

  • If you have one account on a platform, use the simple string: "threads"
  • If you have multiple accounts, specify which one: { "platform": "x", "accountId": "x_primary" }
  • Omitting accountId when you have multiple accounts returns a 400 error

Step 3: Upload media and post with it

Some platforms require media URLs that are stable and publicly accessible. Use the /v1/media endpoint to upload media first:

# Upload an image
MEDIA_URL=$(curl -s -X POST "$SOKU_BASE_URL/v1/media" \
-H "Content-Type: application/json" \
-H "soku-api-key: $SOKU_API_KEY" \
-d '{ "url": "https://picsum.photos/seed/soku/1080/1350.jpg" }' | jq -r .url)

echo "Uploaded to: $MEDIA_URL"

Now use that URL in a post:

curl -X POST "$SOKU_BASE_URL/v1/posts" \
-H "Content-Type: application/json" \
-H "soku-api-key: $SOKU_API_KEY" \
-d "{
\"post\": {
\"content\": {
\"text\": \"Photo post from the API\",
\"imageUrls\": [\"$MEDIA_URL\"],
\"platform\": [
{ \"platform\": \"instagram\", \"accountId\": \"ig_creator_main\" }
]
}
}
}"

When to upload media first:

  • The source URL might be temporary or behind authentication
  • You want a consistent, fast-loading URL across all platforms
  • You're posting to Instagram (which requires publicly accessible media)

Step 4: Schedule a post for later

Add the scheduledTime field with an ISO 8601 timestamp:

curl -X POST "$SOKU_BASE_URL/v1/posts" \
-H "Content-Type: application/json" \
-H "soku-api-key: $SOKU_API_KEY" \
-d '{
"post": {
"content": {
"text": "Scheduled post",
"platform": ["x"]
}
},
"scheduledTime": "2030-01-01T12:00:00Z"
}'

Rules:

  • The time must be a valid ISO 8601 datetime (e.g., 2025-09-01T08:00:00Z)
  • It must be at least 1 minute in the future
  • If omitted or null, the post publishes immediately

Step 5: Use idempotency for safe retries

If your request might be retried (network issues, timeouts), use the Idempotency-Key header to prevent duplicates:

curl -X POST "$SOKU_BASE_URL/v1/posts" \
-H "Content-Type: application/json" \
-H "soku-api-key: $SOKU_API_KEY" \
-H "Idempotency-Key: my-unique-request-001" \
-d '{
"post": {
"content": {
"text": "This will only post once, even if retried",
"platform": ["x", "threads"]
}
}
}'

If you send this exact same request again with the same key, you'll get back the same postSubmissionId without creating a duplicate.

Note: If you omit the header, Soku automatically derives a key from your user ID and request body. This means identical requests naturally deduplicate.


Tracking your post

After submitting, the post goes through these stages:

  1. queued — Submission created, orchestration task enqueued
  2. orchestrating — The system is resolving targets and dispatching to platforms
  3. dispatched — Platform-specific publish tasks are running

Check the status in Firestore:

  • Submission: postSubmissions/{postSubmissionId}
  • Per-platform results: postSubmissions/{postSubmissionId}/runs/{platform}
  • API request logs: users/{uid}/apiRequests

Each platform run has its own status: queuedprocessingsucceeded or failed.


Troubleshooting

ErrorCauseFix
401 unauthorizedBad API keyCheck soku-api-key header. Ensure the key is not revoked.
403 forbiddenSubscription issueVerify subscription is active or trialing and not expired.
400 validation_errorMalformed requestCheck the details array for the specific field that failed.
400 missing_integrationsPlatform not connectedConnect the platform in the web app Integrations page.
409 idempotency_conflictKey reuse with different bodyUse a new Idempotency-Key for different requests.
429 rate_limit_exceededToo many requestsWait for Retry-After seconds, then retry.
Post queued but never publishesToken expiredRe-authenticate the platform in the web app.

Debug tips:

  • Every response includes an X-Request-ID header — save it for support inquiries
  • Send your own X-Request-ID header to correlate requests with logs
  • Check postSubmissions/{id}/runs/{platform} for per-platform error details

Next steps