JavaScript Examples

Complete JavaScript and Node.js code examples for using the Boring API.

Installation

Install required packages:

npm install axios
# or
npm install node-fetch

Basic Setup (Node.js)

Using Axios

const axios = require('axios');

// Configuration
const API_URL = "https://boring.aiagent-me.com/v2/posts";
const API_KEY = process.env.BORING_API_KEY;

// Headers
const headers = {
  "boring-api-key": API_KEY,
  "Content-Type": "application/json"
};

Using Fetch (Node.js 18+)

// Configuration
const API_URL = "https://boring.aiagent-me.com/v2/posts";
const API_KEY = process.env.BORING_API_KEY;

const headers = {
  "boring-api-key": API_KEY,
  "Content-Type": "application/json"
};

Simple Publishing Function

Axios Version

async function publishPost(accountId, platform, text, mediaUrls = []) {
  const data = {
    post: {
      accountId: accountId,
      content: {
        text: text,
        mediaUrls: mediaUrls,
        platform: platform
      },
      target: {
        targetType: platform
      }
    }
  };

  try {
    const response = await axios.post(API_URL, data, { headers });
    return response.data;
  } catch (error) {
    console.error("API Error:", error.response?.data || error.message);
    throw error;
  }
}

Fetch Version

async function publishPost(accountId, platform, text, mediaUrls = []) {
  const data = {
    post: {
      accountId: accountId,
      content: {
        text: text,
        mediaUrls: mediaUrls,
        platform: platform
      },
      target: {
        targetType: platform
      }
    }
  };

  const response = await fetch(API_URL, {
    method: "POST",
    headers: headers,
    body: JSON.stringify(data)
  });

  const result = await response.json();

  if (!result.success) {
    throw new Error(result.message);
  }

  return result;
}

Facebook Examples

Text-Only Post

const result = await publishPost(
  "your-facebook-account-id",
  "facebook",
  "Hello from Boring API! πŸš€"
);

console.log(`Published! Post ID: ${result.data.post_id}`);

Single Photo

const result = await publishPost(
  "your-facebook-account-id",
  "facebook",
  "Check out our new product!",
  ["https://example.com/product.jpg"]
);

Photo Album

const result = await publishPost(
  "your-facebook-account-id",
  "facebook",
  "Our best moments from the event! πŸ“Έ",
  [
    "https://example.com/event1.jpg",
    "https://example.com/event2.jpg",
    "https://example.com/event3.jpg",
    "https://example.com/event4.jpg"
  ]
);

Video Post

const result = await publishPost(
  "your-facebook-account-id",
  "facebook",
  "Watch our product demo! πŸŽ₯",
  ["https://example.com/demo.mp4"]
);

Instagram Examples

Single Photo

const result = await publishPost(
  "your-instagram-account-id",
  "instagram",
  "Beautiful sunset πŸŒ…\n\n#photography #nature #sunset",
  ["https://example.com/sunset.jpg"]
);

Carousel

const result = await publishPost(
  "your-instagram-account-id",
  "instagram",
  "Swipe to see our new collection! ➑️\n\n#fashion #style #newcollection",
  [
    "https://example.com/item1.jpg",
    "https://example.com/item2.jpg",
    "https://example.com/item3.jpg"
  ]
);

Reels

const result = await publishPost(
  "your-instagram-account-id",
  "instagram",
  "Quick tutorial! πŸŽ₯\n\n#tutorial #howto #tips",
  ["https://example.com/tutorial.mp4"]
);

Threads Examples

Text-Only

const result = await publishPost(
  "your-threads-account-id",
  "threads",
  "Just shipped a new feature! πŸš€"
);

Long-Form Thread

const threadContent = [
  "🧡 Let me tell you about building our API (thread)",
  "It started with a simple idea: make social media publishing easier.",
  "We researched all the pain points developers face.",
  "Token management, platform differences, complex APIs - all frustrating.",
  "So we built Boring: one unified API for all platforms.",
  "Today we support Facebook, Instagram, and Threads!",
  "What platform should we add next? Let me know! πŸ‘‡"
];

const result = await publishPost(
  "your-threads-account-id",
  "threads",
  threadContent  // Array creates a thread!
);

console.log(`Thread published! ${result.data.thread_count} posts`);
console.log(`Post IDs: ${result.data.post_ids.join(', ')}`);

Advanced Examples

Cross-Platform Publishing

async function crossPlatformPublish(accounts, text, mediaUrls) {
  const results = {};

  for (const [platform, accountId] of Object.entries(accounts)) {
    console.log(`Publishing to ${platform}...`);

    try {
      // Adapt content for each platform
      const adaptedText = adaptContent(text, platform);
      const adaptedMedia = adaptMedia(mediaUrls, platform);

      const result = await publishPost(
        accountId,
        platform,
        adaptedText,
        adaptedMedia
      );

      results[platform] = {
        success: true,
        data: result.data
      };

      console.log(`βœ“ ${platform}: Success`);

    } catch (error) {
      results[platform] = {
        success: false,
        error: error.message
      };

      console.error(`βœ— ${platform}: ${error.message}`);
    }
  }

  return results;
}

// Usage
const accounts = {
  facebook: "fb-account-id",
  instagram: "ig-account-id",
  threads: "threads-account-id"
};

const results = await crossPlatformPublish(
  accounts,
  "Check out our new product launch!",
  ["https://example.com/product.jpg"]
);

Content Adaptation

function adaptContent(text, platform) {
  if (platform === "instagram") {
    return `${text}\n\n#product #launch #new`;
  } else if (platform === "threads") {
    return `${text} What do you think? πŸ’­`;
  } else {
    return text;
  }
}

function adaptMedia(mediaUrls, platform) {
  if (platform === "instagram" && mediaUrls.length === 0) {
    throw new Error("Instagram requires media");
  }

  // Threads supports up to 20, others up to 10
  const limit = platform === "threads" ? 20 : 10;
  return mediaUrls.slice(0, limit);
}

Error Handling

async function publishWithErrorHandling(accountId, platform, text, mediaUrls = []) {
  try {
    console.log(`Publishing to ${platform}...`);

    const result = await publishPost(accountId, platform, text, mediaUrls);

    console.log(`Success! Post ID: ${result.data.post_id}`);
    return result;

  } catch (error) {
    const errorData = error.response?.data;

    if (!errorData) {
      console.error("Network error:", error.message);
      return { success: false, error: "NetworkError" };
    }

    const errorCode = errorData.error;

    switch (errorCode) {
      case "TokenExpired":
        console.error("Token expired. Please reconnect account.");
        await notifyAdmin("Token expired", accountId);
        break;

      case "MediaDownloadFailed":
        console.error("Media download failed. Retrying without media...");
        return await publishPost(accountId, platform, text, []);

      case "RateLimitExceeded":
        const retryAfter = errorData.retry_after || 3600;
        console.warn(`Rate limited. Retry in ${retryAfter}s`);
        break;

      default:
        console.error(`Error: ${errorData.message}`);
    }

    return errorData;
  }
}

async function notifyAdmin(subject, details) {
  console.log(`ADMIN ALERT: ${subject} - ${details}`);
  // Implement your notification logic
}

Retry Logic

async function publishWithRetry(accountId, platform, text, mediaUrls = [], maxRetries = 3) {
  const retryableErrors = [
    "RateLimitExceeded",
    "ServiceUnavailable",
    "Timeout",
    "InternalServerError"
  ];

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      console.log(`Attempt ${attempt + 1}/${maxRetries}`);

      const result = await publishPost(accountId, platform, text, mediaUrls);
      return result;

    } catch (error) {
      const errorData = error.response?.data;
      const errorCode = errorData?.error;

      // Don't retry non-retryable errors
      if (!retryableErrors.includes(errorCode)) {
        console.warn(`Non-retryable error: ${errorCode}`);
        throw error;
      }

      // Calculate wait time
      let waitTime;
      if (errorCode === "RateLimitExceeded") {
        waitTime = errorData.retry_after || 3600;
      } else {
        waitTime = Math.pow(2, attempt);  // Exponential backoff
      }

      console.log(`Retrying in ${waitTime}s...`);
      await sleep(waitTime * 1000);
    }
  }

  throw new Error("Max retries exceeded");
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Batch Publishing

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

  for (let i = 0; i < posts.length; i++) {
    console.log(`Publishing post ${i + 1}/${posts.length}`);

    try {
      const result = await publishWithRetry(
        posts[i].accountId,
        posts[i].platform,
        posts[i].text,
        posts[i].mediaUrls
      );

      results.push({
        index: i,
        post: posts[i],
        result: result
      });

    } catch (error) {
      results.push({
        index: i,
        post: posts[i],
        result: { success: false, error: error.message }
      });
    }

    // Rate limiting
    if (i < posts.length - 1) {
      await sleep(delay);
    }
  }

  // Summary
  const successes = results.filter(r => r.result.success).length;
  const failures = results.length - successes;

  console.log(`Batch complete: ${successes} succeeded, ${failures} failed`);

  return results;
}

// Usage
const posts = [
  {
    accountId: "fb-account-id",
    platform: "facebook",
    text: "Post 1",
    mediaUrls: []
  },
  {
    accountId: "ig-account-id",
    platform: "instagram",
    text: "Post 2",
    mediaUrls: ["https://example.com/image.jpg"]
  },
  {
    accountId: "threads-account-id",
    platform: "threads",
    text: "Post 3",
    mediaUrls: []
  }
];

const results = await batchPublish(posts);

Thread Generator

function splitIntoThread(longText, maxChars = 450) {
  const sentences = longText.split('. ');
  const posts = [];
  let currentPost = "";

  for (const sentence of sentences) {
    if (currentPost.length + sentence.length + 2 <= maxChars) {
      currentPost += sentence + '. ';
    } else {
      if (currentPost) {
        posts.push(currentPost.trim());
      }
      currentPost = sentence + '. ';
    }
  }

  if (currentPost) {
    posts.push(currentPost.trim());
  }

  return posts;
}

// Usage
const article = `
This is a very long article about our product launch.
It contains multiple sentences and paragraphs.
We want to share it as a thread on Threads.
Each post should be under 450 characters.
This function will automatically split it for us.
`;

const threadPosts = splitIntoThread(article);

const result = await publishPost(
  "threads-account-id",
  "threads",
  threadPosts  // Array creates thread
);

Configuration Class

const fs = require('fs').promises;

class BoringAPI {
  constructor(apiKey, accountsConfigPath) {
    this.apiKey = apiKey || process.env.BORING_API_KEY;
    this.apiUrl = "https://boring.aiagent-me.com/v2/posts";
    this.accountsConfigPath = accountsConfigPath;
    this.accounts = null;

    this.headers = {
      "boring-api-key": this.apiKey,
      "Content-Type": "application/json"
    };
  }

  async loadAccounts() {
    if (this.accountsConfigPath) {
      const data = await fs.readFile(this.accountsConfigPath, 'utf8');
      this.accounts = JSON.parse(data);
    }
  }

  async publish(accountName, text, mediaUrls = []) {
    if (!this.accounts) {
      await this.loadAccounts();
    }

    if (!this.accounts[accountName]) {
      throw new Error(`Account '${accountName}' not found in config`);
    }

    const account = this.accounts[accountName];

    return await publishPost(
      account.id,
      account.platform,
      text,
      mediaUrls
    );
  }
}

// accounts.json
{
  "production_fb": {
    "id": "fb-account-id",
    "platform": "facebook"
  },
  "production_ig": {
    "id": "ig-account-id",
    "platform": "instagram"
  },
  "production_threads": {
    "id": "threads-account-id",
    "platform": "threads"
  }
}

// Usage
const api = new BoringAPI(null, "accounts.json");

await api.publish(
  "production_fb",
  "Hello from Boring API!",
  []
);

Browser (Frontend) Example

<!DOCTYPE html>
<html>
<head>
  <title>Boring API Test</title>
</head>
<body>
  <h1>Publish to Social Media</h1>

  <form id="publishForm">
    <label>Account ID:</label>
    <input type="text" id="accountId" required><br>

    <label>Platform:</label>
    <select id="platform">
      <option value="facebook">Facebook</option>
      <option value="instagram">Instagram</option>
      <option value="threads">Threads</option>
    </select><br>

    <label>Text:</label>
    <textarea id="text" rows="4" required></textarea><br>

    <label>Media URLs (one per line):</label>
    <textarea id="mediaUrls" rows="3"></textarea><br>

    <button type="submit">Publish</button>
  </form>

  <div id="result"></div>

  <script>
    const API_URL = "https://boring.aiagent-me.com/v2/posts";
    const API_KEY = "YOUR_API_KEY_HERE";  // WARNING: Don't expose in production!

    document.getElementById('publishForm').addEventListener('submit', async (e) => {
      e.preventDefault();

      const accountId = document.getElementById('accountId').value;
      const platform = document.getElementById('platform').value;
      const text = document.getElementById('text').value;
      const mediaUrlsText = document.getElementById('mediaUrls').value;

      const mediaUrls = mediaUrlsText
        .split('\n')
        .map(url => url.trim())
        .filter(url => url);

      const data = {
        post: {
          accountId: accountId,
          content: {
            text: text,
            mediaUrls: mediaUrls,
            platform: platform
          },
          target: {
            targetType: platform
          }
        }
      };

      try {
        const response = await fetch(API_URL, {
          method: "POST",
          headers: {
            "boring-api-key": API_KEY,
            "Content-Type": "application/json"
          },
          body: JSON.stringify(data)
        });

        const result = await response.json();

        if (result.success) {
          document.getElementById('result').innerHTML =
            `<div style="color: green;">Success! Post ID: ${result.data.post_id}</div>`;
        } else {
          document.getElementById('result').innerHTML =
            `<div style="color: red;">Error: ${result.message}</div>`;
        }

      } catch (error) {
        document.getElementById('result').innerHTML =
          `<div style="color: red;">Network error: ${error.message}</div>`;
      }
    });
  </script>
</body>
</html>

Note: Never expose your API key in frontend code in production! Use a backend proxy instead.

Complete Example (Node.js)

#!/usr/bin/env node

/**
 * Boring API Publishing Script
 */

const axios = require('axios');

// Configuration
const API_URL = "https://boring.aiagent-me.com/v2/posts";
const API_KEY = process.env.BORING_API_KEY;

const headers = {
  "boring-api-key": API_KEY,
  "Content-Type": "application/json"
};

async function publishPost(accountId, platform, text, mediaUrls = []) {
  const data = {
    post: {
      accountId,
      content: {
        text,
        mediaUrls,
        platform
      },
      target: {
        targetType: platform
      }
    }
  };

  const response = await axios.post(API_URL, data, { headers });
  return response.data;
}

async function main() {
  console.log("Publishing to Facebook...");

  const result = await publishPost(
    process.env.FB_ACCOUNT_ID,
    "facebook",
    "Hello from Boring API! πŸš€",
    []
  );

  if (result.success) {
    console.log(`Success! Post ID: ${result.data.post_id}`);
  } else {
    console.error(`Failed: ${result.message}`);
  }
}

main().catch(console.error);

Next Steps