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:
- A Soku account with an active subscription
- An API key — create one in the web app at Settings > API Keys
- 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
accountIdwhen you have multiple accounts returns a400error
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:
- queued — Submission created, orchestration task enqueued
- orchestrating — The system is resolving targets and dispatching to platforms
- 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: queued → processing → succeeded or failed.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
401 unauthorized | Bad API key | Check soku-api-key header. Ensure the key is not revoked. |
403 forbidden | Subscription issue | Verify subscription is active or trialing and not expired. |
400 validation_error | Malformed request | Check the details array for the specific field that failed. |
400 missing_integrations | Platform not connected | Connect the platform in the web app Integrations page. |
409 idempotency_conflict | Key reuse with different body | Use a new Idempotency-Key for different requests. |
429 rate_limit_exceeded | Too many requests | Wait for Retry-After seconds, then retry. |
| Post queued but never publishes | Token expired | Re-authenticate the platform in the web app. |
Debug tips:
- Every response includes an
X-Request-IDheader — save it for support inquiries - Send your own
X-Request-IDheader to correlate requests with logs - Check
postSubmissions/{id}/runs/{platform}for per-platform error details
Next steps
- Public API Reference — Full endpoint documentation with all fields
- Rate Limiting — Understand request limits and tiers
- API Key Lifecycle — Managing your API keys
- Orchestration Architecture — How publishing works under the hood