Skip to main content

Code Examples

This page provides complete workflow examples for common Soku API tasks. Each example is shown in both bash/curl and JavaScript (Node.js) to help you get started quickly.

Setup

Environment Variable

All examples assume your API key is stored in an environment variable:
export SOKU_API_KEY="sk_live_your_api_key_here"

JavaScript Base Configuration

The JavaScript examples use the built-in fetch API available in Node.js 18+. Here is a reusable helper function used throughout this page:
const SOKU_API_KEY = process.env.SOKU_API_KEY;
const BASE_URL = 'https://api.mysoku.io';

async function sokuRequest(path, options = {}) {
  const url = `${BASE_URL}${path}`;
  const headers = {
    'Content-Type': 'application/json',
    'soku-api-key': SOKU_API_KEY,
    ...options.headers,
  };

  const response = await fetch(url, {
    ...options,
    headers,
  });

  if (!response.ok) {
    const body = await response.json();
    const error = new Error(body.error?.message || 'API request failed');
    error.code = body.error?.code;
    error.status = response.status;
    error.requestId = body.error?.requestId;
    throw error;
  }

  // Handle 204 No Content
  if (response.status === 204) {
    return null;
  }

  return response.json();
}

Publish a Text Post

Post a text update to multiple platforms at once.

bash

curl -X POST https://api.mysoku.io/v1/posts \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d '{
    "post": {
      "content": {
        "text": "Just shipped our biggest update yet. More details coming soon.",
        "mediaType": "text",
        "platform": ["threads", "x", "linkedin"]
      }
    }
  }'

JavaScript

async function publishTextPost() {
  const result = await sokuRequest('/v1/posts', {
    method: 'POST',
    body: JSON.stringify({
      post: {
        content: {
          text: 'Just shipped our biggest update yet. More details coming soon.',
          mediaType: 'text',
          platform: ['threads', 'x', 'linkedin'],
        },
      },
    }),
  });

  console.log('Post submitted:', result.postSubmissionId);
  return result;
}

Upload Media and Publish a Video Post

Upload a video from a remote URL, then publish it to multiple platforms.

bash

# Step 1: Upload the video
MEDIA_RESPONSE=$(curl -s -X POST https://api.mysoku.io/v1/media \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d '{
    "url": "https://example.com/videos/product-demo.mp4"
  }')

MEDIA_URL=$(echo "$MEDIA_RESPONSE" | jq -r '.url')
echo "Uploaded media: $MEDIA_URL"

# Step 2: Publish the video post
curl -X POST https://api.mysoku.io/v1/posts \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d "{
    \"post\": {
      \"content\": {
        \"text\": \"Watch our full product walkthrough\",
        \"mediaType\": \"video\",
        \"videoUrl\": \"$MEDIA_URL\",
        \"platform\": [
          {\"platform\": \"instagram\", \"accountId\": \"ig_main\"},
          {\"platform\": \"tiktok\", \"accountId\": \"tt_brand\"},
          \"youtube\"
        ]
      }
    }
  }"

JavaScript

async function uploadAndPublishVideo() {
  // Step 1: Upload the video
  const media = await sokuRequest('/v1/media', {
    method: 'POST',
    body: JSON.stringify({
      url: 'https://example.com/videos/product-demo.mp4',
    }),
  });

  console.log('Uploaded media:', media.url);

  // Step 2: Publish the video post
  const result = await sokuRequest('/v1/posts', {
    method: 'POST',
    body: JSON.stringify({
      post: {
        content: {
          text: 'Watch our full product walkthrough',
          mediaType: 'video',
          videoUrl: media.url,
          platform: [
            { platform: 'instagram', accountId: 'ig_main' },
            { platform: 'tiktok', accountId: 'tt_brand' },
            'youtube',
          ],
        },
      },
    }),
  });

  console.log('Post submitted:', result.postSubmissionId);
  return result;
}

Publish an Image Post with Multiple Images

bash

curl -X POST https://api.mysoku.io/v1/posts \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d '{
    "post": {
      "content": {
        "text": "Behind the scenes from today'\''s shoot",
        "mediaType": "image",
        "imageUrls": [
          "https://storage.mysoku.io/media/img1.jpg",
          "https://storage.mysoku.io/media/img2.jpg",
          "https://storage.mysoku.io/media/img3.jpg"
        ],
        "platform": [
          { "platform": "instagram", "accountId": "ig_main" },
          { "platform": "facebook", "accountId": "fb_page" },
          "linkedin"
        ]
      }
    }
  }'

JavaScript

async function publishImagePost() {
  const result = await sokuRequest('/v1/posts', {
    method: 'POST',
    body: JSON.stringify({
      post: {
        content: {
          text: "Behind the scenes from today's shoot",
          mediaType: 'image',
          imageUrls: [
            'https://storage.mysoku.io/media/img1.jpg',
            'https://storage.mysoku.io/media/img2.jpg',
            'https://storage.mysoku.io/media/img3.jpg',
          ],
          platform: [
            { platform: 'instagram', accountId: 'ig_main' },
            { platform: 'facebook', accountId: 'fb_page' },
            'linkedin',
          ],
        },
      },
    }),
  });

  console.log('Post submitted:', result.postSubmissionId);
  return result;
}

Generate an OG Image and Publish

Render a template image and publish it as an image post.

bash

# Step 1: Render the OG image
TEMPLATE_RESPONSE=$(curl -s -X POST https://api.mysoku.io/v1/templates \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d '{
    "templateSlug": "tweetImage",
    "config": {
      "title": "5 Lessons From Building in Public",
      "author": "Soku Team",
      "theme": "dark"
    }
  }')

IMAGE_URL=$(echo "$TEMPLATE_RESPONSE" | jq -r '.url')
echo "Rendered image: $IMAGE_URL"

# Step 2: Publish the image post
curl -X POST https://api.mysoku.io/v1/posts \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d "{
    \"post\": {
      \"content\": {
        \"text\": \"5 Lessons From Building in Public — a thread.\",
        \"mediaType\": \"image\",
        \"imageUrls\": [\"$IMAGE_URL\"],
        \"platform\": [\"x\", \"linkedin\"]
      }
    }
  }"

JavaScript

async function renderTemplateAndPublish() {
  // Step 1: Render the OG image
  const template = await sokuRequest('/v1/templates', {
    method: 'POST',
    body: JSON.stringify({
      templateSlug: 'tweetImage',
      config: {
        title: '5 Lessons From Building in Public',
        author: 'Soku Team',
        theme: 'dark',
      },
    }),
  });

  console.log('Rendered image:', template.url);

  // Step 2: Publish the image post
  const result = await sokuRequest('/v1/posts', {
    method: 'POST',
    body: JSON.stringify({
      post: {
        content: {
          text: '5 Lessons From Building in Public — a thread.',
          mediaType: 'image',
          imageUrls: [template.url],
          platform: ['x', 'linkedin'],
        },
      },
    }),
  });

  console.log('Post submitted:', result.postSubmissionId);
  return result;
}

Transcribe a Video and Repurpose as Text

Transcribe a video file and publish the transcript as a text post on other platforms.

bash

# Step 1: Upload the video
MEDIA_RESPONSE=$(curl -s -X POST https://api.mysoku.io/v1/media \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d '{
    "url": "https://example.com/podcasts/episode-42.mp4"
  }')

MEDIA_URL=$(echo "$MEDIA_RESPONSE" | jq -r '.url')

# Step 2: Transcribe the video (with idempotency key)
TRANSCRIPTION=$(curl -s -X POST https://api.mysoku.io/v1/ai/transcribe \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -H "Idempotency-Key: ep42-transcribe-$(date +%Y%m%d)" \
  -d '{
    "mediaId": "media_uploaded_id_here",
    "language": "en",
    "model": "whisper-1",
    "durationSeconds": 600
  }')

TRANSCRIPT_TEXT=$(echo "$TRANSCRIPTION" | jq -r '.transcript')
CREDITS_USED=$(echo "$TRANSCRIPTION" | jq -r '.creditsDebited')
echo "Transcription complete. Credits used: $CREDITS_USED"

# Step 3: Publish the transcript as a text post
curl -X POST https://api.mysoku.io/v1/posts \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d "{
    \"post\": {
      \"content\": {
        \"text\": \"Key takeaways from Episode 42:\n\n$TRANSCRIPT_TEXT\",
        \"mediaType\": \"text\",
        \"platform\": [\"threads\", \"linkedin\"]
      }
    }
  }"

JavaScript

async function transcribeAndRepurpose() {
  // Step 1: Upload the video
  const media = await sokuRequest('/v1/media', {
    method: 'POST',
    body: JSON.stringify({
      url: 'https://example.com/podcasts/episode-42.mp4',
    }),
  });

  // Step 2: Transcribe the video
  const transcription = await sokuRequest('/v1/ai/transcribe', {
    method: 'POST',
    headers: {
      'Idempotency-Key': `ep42-transcribe-${new Date().toISOString().slice(0, 10)}`,
    },
    body: JSON.stringify({
      mediaId: 'media_uploaded_id_here',
      language: 'en',
      model: 'whisper-1',
      durationSeconds: 600,
    }),
  });

  console.log(`Transcription complete. Credits used: ${transcription.creditsDebited}`);

  // Step 3: Publish the transcript as a text post
  const result = await sokuRequest('/v1/posts', {
    method: 'POST',
    body: JSON.stringify({
      post: {
        content: {
          text: `Key takeaways from Episode 42:\n\n${transcription.transcript}`,
          mediaType: 'text',
          platform: ['threads', 'linkedin'],
        },
      },
    }),
  });

  console.log('Post submitted:', result.postSubmissionId);
  return result;
}

Schedule a Post for Later

Schedule a post to be published at a specific future time.

bash

curl -X POST https://api.mysoku.io/v1/posts \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d '{
    "post": {
      "content": {
        "text": "Happy Monday! Here is your weekly dose of motivation.",
        "mediaType": "text",
        "platform": ["threads", "x", "linkedin"]
      }
    },
    "scheduledTime": "2026-03-02T14:00:00.000Z"
  }'

JavaScript

async function schedulePost() {
  const result = await sokuRequest('/v1/posts', {
    method: 'POST',
    body: JSON.stringify({
      post: {
        content: {
          text: 'Happy Monday! Here is your weekly dose of motivation.',
          mediaType: 'text',
          platform: ['threads', 'x', 'linkedin'],
        },
      },
      scheduledTime: '2026-03-02T14:00:00.000Z',
    }),
  });

  console.log('Post scheduled:', result.postSubmissionId);
  return result;
}

Create, list, and delete repurpose links for automated content repurposing.

bash

# List existing repurpose links
curl -s -X GET "https://api.mysoku.io/v1/repurposeLinks?platform=instagram" \
  -H "soku-api-key: $SOKU_API_KEY" | jq

# Create a new repurpose link
curl -X POST https://api.mysoku.io/v1/repurposeLinks \
  -H "Content-Type: application/json" \
  -H "soku-api-key: $SOKU_API_KEY" \
  -d '{
    "platform": "instagram",
    "accountId": "ig_main",
    "type": "post",
    "templateId": "tmpl_f7e8d9c0",
    "settings": {
      "includeCaption": true,
      "addHashtags": false
    }
  }'

# Delete a repurpose link
curl -X DELETE https://api.mysoku.io/v1/repurposeLinks/rl_a1b2c3d4 \
  -H "soku-api-key: $SOKU_API_KEY"

JavaScript

async function manageRepurposeLinks() {
  // List existing links filtered by platform
  const links = await sokuRequest('/v1/repurposeLinks?platform=instagram');
  console.log(`Found ${links.length} repurpose links`);

  // Create a new repurpose link
  const newLink = await sokuRequest('/v1/repurposeLinks', {
    method: 'POST',
    body: JSON.stringify({
      platform: 'instagram',
      accountId: 'ig_main',
      type: 'post',
      templateId: 'tmpl_f7e8d9c0',
      settings: {
        includeCaption: true,
        addHashtags: false,
      },
    }),
  });

  console.log('Created repurpose link:', newLink.id);

  // Delete a repurpose link
  await sokuRequest(`/v1/repurposeLinks/${newLink.id}`, {
    method: 'DELETE',
  });

  console.log('Deleted repurpose link:', newLink.id);
}

Error Handling with Retry Logic

A production-ready request function with automatic retry for transient errors.

JavaScript

const SOKU_API_KEY = process.env.SOKU_API_KEY;
const BASE_URL = 'https://api.mysoku.io';

async function sokuRequestWithRetry(path, options = {}, maxRetries = 3) {
  const url = `${BASE_URL}${path}`;
  const headers = {
    'Content-Type': 'application/json',
    'soku-api-key': SOKU_API_KEY,
    ...options.headers,
  };

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, {
      ...options,
      headers,
    });

    // Log rate limit status
    const remaining = response.headers.get('X-RateLimit-Remaining');
    const limit = response.headers.get('X-RateLimit-Limit');
    if (remaining && limit) {
      console.log(`Rate limit: ${remaining}/${limit} remaining`);
    }

    // Handle rate limiting
    if (response.status === 429) {
      if (attempt === maxRetries) {
        const body = await response.json();
        throw new Error(
          `Rate limit exceeded after ${maxRetries} retries (requestId: ${body.error?.requestId})`
        );
      }

      const retryAfter = parseInt(response.headers.get('Retry-After'), 10) || 1;
      const jitter = Math.random() * 1000;
      const delay = retryAfter * 1000 + jitter;
      console.log(`Rate limited. Waiting ${Math.round(delay / 1000)}s before retry ${attempt + 1}...`);
      await new Promise((resolve) => setTimeout(resolve, delay));
      continue;
    }

    // Handle server errors with retry
    if (response.status >= 500) {
      if (attempt === maxRetries) {
        const body = await response.json();
        throw new Error(
          `Server error after ${maxRetries} retries: ${body.error?.code} (requestId: ${body.error?.requestId})`
        );
      }

      const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
      console.log(`Server error. Waiting ${Math.round(delay / 1000)}s before retry ${attempt + 1}...`);
      await new Promise((resolve) => setTimeout(resolve, delay));
      continue;
    }

    // Handle client errors (no retry)
    if (!response.ok) {
      const body = await response.json();
      const error = new Error(body.error?.message || 'API request failed');
      error.code = body.error?.code;
      error.status = response.status;
      error.requestId = body.error?.requestId;
      error.details = body.error?.details;
      throw error;
    }

    // Handle 204 No Content
    if (response.status === 204) {
      return null;
    }

    return response.json();
  }
}

// Usage
async function main() {
  try {
    const result = await sokuRequestWithRetry('/v1/posts', {
      method: 'POST',
      body: JSON.stringify({
        post: {
          content: {
            text: 'Published with production-grade error handling.',
            mediaType: 'text',
            platform: ['threads'],
          },
        },
      }),
    });

    console.log('Post submitted:', result.postSubmissionId);
  } catch (error) {
    if (error.code === 'missing_integrations') {
      console.error('Connect the required platforms in Settings > Integrations.');
    } else if (error.code === 'insufficient_credits') {
      console.error('Purchase more credits at Account > Credits.');
    } else {
      console.error(`Unexpected error: ${error.message} (requestId: ${error.requestId})`);
    }
  }
}

Batch Publishing Workflow

Publish multiple posts sequentially with rate limit awareness.

JavaScript

async function batchPublish(posts) {
  const results = [];

  for (const post of posts) {
    try {
      const result = await sokuRequestWithRetry('/v1/posts', {
        method: 'POST',
        body: JSON.stringify(post),
      });

      results.push({ status: 'success', postSubmissionId: result.postSubmissionId });
      console.log(`Published: ${result.postSubmissionId}`);
    } catch (error) {
      results.push({ status: 'error', code: error.code, message: error.message });
      console.error(`Failed: ${error.code} - ${error.message}`);
    }

    // Small delay between requests to avoid hitting rate limits
    await new Promise((resolve) => setTimeout(resolve, 200));
  }

  const succeeded = results.filter((r) => r.status === 'success').length;
  const failed = results.filter((r) => r.status === 'error').length;
  console.log(`Batch complete: ${succeeded} succeeded, ${failed} failed`);

  return results;
}

// Usage
const posts = [
  {
    post: {
      content: {
        text: 'Monday motivation: consistency beats perfection.',
        mediaType: 'text',
        platform: ['threads', 'x'],
      },
    },
    scheduledTime: '2026-03-02T09:00:00.000Z',
  },
  {
    post: {
      content: {
        text: 'Tuesday tip: automate what you repeat.',
        mediaType: 'text',
        platform: ['threads', 'x'],
      },
    },
    scheduledTime: '2026-03-03T09:00:00.000Z',
  },
  {
    post: {
      content: {
        text: 'Wednesday wisdom: ship it, then improve.',
        mediaType: 'text',
        platform: ['threads', 'x'],
      },
    },
    scheduledTime: '2026-03-04T09:00:00.000Z',
  },
];

batchPublish(posts);

API Key Rotation

Programmatically rotate an API key. This example uses Firebase ID token authentication for the key management endpoints.

bash

FIREBASE_TOKEN="eyJhbGciOiJSUzI1NiIs..."

# Step 1: Create a new key
NEW_KEY_RESPONSE=$(curl -s -X POST https://api.mysoku.io/v1/api-keys \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $FIREBASE_TOKEN" \
  -d '{"name": "Production Server (rotated)"}')

NEW_KEY=$(echo "$NEW_KEY_RESPONSE" | jq -r '.apiKey')
echo "New key created"

# Step 2: Verify the new key works
curl -s -X GET https://api.mysoku.io/v1/repurposeLinks \
  -H "soku-api-key: $NEW_KEY" | jq '.| length'

# Step 3: Update your application environment variable
echo "Update SOKU_API_KEY to: $NEW_KEY"

# Step 4: Revoke the old key
OLD_KEY_ID="key_old_id_here"
curl -X DELETE "https://api.mysoku.io/v1/api-keys/$OLD_KEY_ID" \
  -H "Authorization: Bearer $FIREBASE_TOKEN"

echo "Old key revoked: $OLD_KEY_ID"

JavaScript

async function rotateApiKey(firebaseToken, oldKeyId) {
  const headers = {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${firebaseToken}`,
  };

  // Step 1: Create a new key
  const createResponse = await fetch('https://api.mysoku.io/v1/api-keys', {
    method: 'POST',
    headers,
    body: JSON.stringify({ name: 'Production Server (rotated)' }),
  });

  const { apiKey: newApiKey } = await createResponse.json();
  console.log('New key created');

  // Step 2: Verify the new key works
  const testResponse = await fetch('https://api.mysoku.io/v1/repurposeLinks', {
    headers: { 'soku-api-key': newApiKey },
  });

  if (!testResponse.ok) {
    throw new Error('New key verification failed');
  }

  console.log('New key verified successfully');

  // Step 3: Revoke the old key
  await fetch(`https://api.mysoku.io/v1/api-keys/${oldKeyId}`, {
    method: 'DELETE',
    headers: { Authorization: `Bearer ${firebaseToken}` },
  });

  console.log('Old key revoked:', oldKeyId);

  // Return the new key for your application to use
  return newApiKey;
}

Next Steps

TopicDescription
IntroductionAPI overview and base URL
AuthenticationAPI key setup and security
Posts APIFull endpoint reference for post creation
Error HandlingComplete error code reference
Rate LimitsRate limit tiers and best practices