> ## Documentation Index
> Fetch the complete documentation index at: https://docs.squarecloud.app/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Build and Host an X (Twitter) Bot

> Build and host an X (Twitter) bot on Square Cloud with Node.js and twitter-api-v2: set up the API keys, reply to new mentions and deploy it.

export const RecommendedPlan = ({lang, plan, tier, cpu, appType, min}) => {
  const t = {
    'en': {
      q: <>Which plan should you choose for {appType}?</>,
      rec: p => <>We recommend the {p} plan: its <b>{tier} GB</b> of RAM and <b>{cpu} vCPU</b> handle most projects of this kind, with headroom for traffic spikes.</>,
      min: m => <> Lighter projects also run on the {m} plan.</>,
      pro: p => <> For larger projects, the {p} plan adds more RAM and CPU.</>
    },
    'pt-br': {
      q: <>Qual plano escolher para {appType}?</>,
      rec: p => <>Recomendamos o plano {p}: seus <b>{tier} GB</b> de RAM e <b>{cpu} vCPU</b> atendem a maioria dos projetos desse tipo, com folga para picos de tráfego.</>,
      min: m => <> Projetos mais leves também rodam no plano {m}.</>,
      pro: p => <> Para projetos maiores, o plano {p} oferece mais RAM e CPU.</>
    },
    'es': {
      q: <>¿Qué plan elegir para {appType}?</>,
      rec: p => <>Te recomendamos el plan {p}: sus <b>{tier} GB</b> de RAM y <b>{cpu} vCPU</b> cubren la mayoría de los proyectos de este tipo, con margen para picos de tráfico.</>,
      min: m => <> Los proyectos más ligeros también funcionan en el plan {m}.</>,
      pro: p => <> Para proyectos más grandes, el plan {p} ofrece más RAM y CPU.</>
    },
    'fr': {
      q: <>Quel plan choisir pour héberger {appType} ?</>,
      rec: p => <>Nous recommandons le plan {p} : ses <b>{tier} Go</b> de RAM et <b>{cpu} vCPU</b> suffisent pour la plupart des projets de ce type, avec une marge pour les pics de trafic.</>,
      min: m => <> Les projets plus légers fonctionnent aussi avec le plan {m}.</>,
      pro: p => <> Pour les projets plus importants, le plan {p} offre davantage de RAM et de CPU.</>
    },
    'de': {
      q: <>Welcher Plan eignet sich für {appType}?</>,
      rec: p => <>Wir empfehlen den {p}-Plan: <b>{tier} GB</b> RAM und <b>{cpu} vCPU</b> reichen für die meisten Projekte dieser Art, mit Reserven für Lastspitzen.</>,
      min: m => <> Kleinere Projekte laufen auch mit dem {m}-Plan.</>,
      pro: p => <> Für größere Projekte bietet der {p}-Plan mehr RAM und CPU.</>
    },
    'it': {
      q: <>Quale piano scegliere per {appType}?</>,
      rec: p => <>Ti consigliamo il piano {p}: i suoi <b>{tier}GB</b> di RAM e <b>{cpu}vCPU</b> bastano per la maggior parte dei progetti di questo tipo, con margine per i picchi di traffico.</>,
      min: m => <> I progetti più leggeri funzionano anche con il piano {m}.</>,
      pro: p => <> Per progetti più grandi, il piano {p} offre più RAM e CPU.</>
    },
    'ja': {
      q: <>{appType}にはどのプランを選べばよいですか？</>,
      rec: p => <>{p}プランをおすすめします。<b>{tier}GB</b>のRAMと<b>{cpu}vCPU</b>があれば、この種のプロジェクトの多くをトラフィックのピークにも余裕を持って動かせます。</>,
      min: m => <>軽量なプロジェクトは{m}プランでも動作します。</>,
      pro: p => <>より大規模なプロジェクトには、RAMとCPUが増える{p}プランをご検討ください。</>
    },
    'zh': {
      q: <>托管{appType}应该选择哪个套餐？</>,
      rec: p => <>我们推荐 {p} 套餐：<b>{tier}GB</b> RAM 和 <b>{cpu}vCPU</b> 足以运行大多数此类项目，并能从容应对流量高峰。</>,
      min: m => <>较轻量的项目也可以在 {m} 套餐上运行。</>,
      pro: p => <>对于更大的项目，{p} 套餐提供更多 RAM 和 CPU。</>
    }
  };
  const siteLang = t[lang] ? lang : 'en';
  const c = t[siteLang];
  const pay = (name, query) => <a href={`https://squarecloud.app/${siteLang}/pay?${query}`}>{name}</a>;
  const isPro = plan.toLowerCase() === 'pro';
  return <Note>
      <b>{c.q}</b><br />
      {c.rec(pay(plan, `plan=${plan.toLowerCase()}&tier=${tier}`))}
      {min ? c.min(pay(min, `plan=${min.toLowerCase()}`)) : null}
      {isPro ? null : c.pro(pay('Pro', 'plan=pro'))}
    </Note>;
};

export const appType_0 = "an X (Twitter) bot"

## Introduction

To develop and host {appType_0} on Square Cloud, it's essential to follow a structured sequence of configurations and prerequisites. This technical guide will cover the entire process, from initial setup to production deployment.

### Prerequisites

* **Square Cloud Account**: Register through the [signup page](https://squarecloud.app/en/signup) using your email.
* **Active plan**: Ensures dedicated resources and optimized performance for your application. Check our [available plans](https://squarecloud.app/en/pricing) and choose the most suitable for your needs.

<RecommendedPlan appType="X (Twitter) bots" plan="Standard" tier="4" cpu="4" min="Hobby" lang="en" />

This guide runs on the [Node.js runtime](/en/articles/getting-started-with-nodejs).

## Create the project

An active X (Twitter) account is required for authentication and bot operation. If you don't have an account, sign up at the [official X website](https://x.com/).\
An X Developer account is also required to access the APIs. Request access through the [X Developer Portal](https://developer.x.com/en).

<Warning>The X API is paid per use: reading mentions and posting replies are billed to your X developer account, including the bot's periodic mention checks. Check the current rates on the [X API pricing page](https://docs.x.com/x-api/getting-started/pricing) before deploying.</Warning>

### Bot application on X

<Steps>
  <Step title="Open the Developer Portal">
    1. Go to the [X Developer Portal](https://developer.x.com/en/portal/dashboard).
    2. Log in with your X (Twitter) account.
    3. If this is your first time, complete the developer access request process.
  </Step>

  <Step title="Create a project">
    1. In the dashboard, click "Create Project".
    2. Choose a name for your project (e.g. "Square Cloud Bot").
    3. Select the most appropriate use case (e.g. "Making a bot").
    4. Provide a detailed description of your bot.
    5. Confirm project creation.
  </Step>

  <Step title="Create an app">
    1. Within the created project, click "Create App".
    2. Define a unique name for your application.
    3. Confirm application creation.
    4. Note down the generated **App ID** for future reference.
  </Step>

  <Step title="Generate the API keys">
    1. Navigate to the "Keys and tokens" section of your application.
    2. In the "Consumer Keys" section, click "Regenerate" to generate:
       * **API Key** (Consumer Key)
       * **API Secret Key** (Consumer Secret)
    3. **Important**: Copy and save these keys immediately, as you won't be able to view them again.
  </Step>

  <Step title="Set the app permissions">
    1. Go to the "App permissions" section.
    2. Click "Edit" to modify permissions.
    3. Select "Read and write" to allow your bot to post tweets.
    4. If necessary, select "Read and write and Direct message" for DM functionality.
    5. Save the changes.
  </Step>

  <Step title="Generate the access tokens">
    1. Return to the "Keys and tokens" section.
    2. In the "Access Token and Secret" section, click "Generate".
    3. Confirm token generation.
    4. Copy and save:
       * **Access Token**
       * **Access Token Secret**
    5. **Warning**: These tokens cannot be viewed again after closing the page.
  </Step>

  <Step title="Check your credentials">
    1. Confirm that you have all 4 required credentials:
       * API Key (Consumer Key)
       * API Secret Key (Consumer Secret)
       * Access Token
       * Access Token Secret
    2. Store these credentials in a secure location
    3. **Important**: Never share or expose these credentials publicly
  </Step>
</Steps>

## Develop the bot

1. **Node.js check**: Verify that Node.js is installed on your system. If not, download it from the [official Node.js website](https://nodejs.org/).

2. **Project initialization**: Create a new Node.js project by running:

```bash Terminal theme={"system"}
npm init -y
```

3. **Install dependencies**: Install the libraries required for the bot:

```bash Terminal theme={"system"}
npm install twitter-api-v2
```

4. **Credentials**: the code reads the four credentials from environment variables, so they never go into your code. To test the bot on your computer, put them in a `.env` file and start the bot with `node --env-file=.env index.js`:

```env .env theme={"system"}
API_KEY=your_api_key_here
API_SECRET_KEY=your_api_secret_key_here
ACCESS_TOKEN=your_access_token_here
ACCESS_TOKEN_SECRET=your_access_token_secret_here
```

Square Cloud doesn't read this file when the bot runs: you set the same variables on the platform in [Set the credentials](#set-the-credentials).

5. **Create the main file**: Develop the `index.js` file with the bot's base structure:

```javascript index.js theme={"system"}
// Import necessary modules
const { TwitterApi } = require('twitter-api-v2');

// Configure Twitter client with authentication
const client = new TwitterApi({
  appKey: process.env.API_KEY,
  appSecret: process.env.API_SECRET_KEY,
  accessToken: process.env.ACCESS_TOKEN,
  accessSecret: process.env.ACCESS_TOKEN_SECRET,
});

// Client with read and write permissions
const rwClient = client.readWrite;

// Stores the authenticated bot user id (set in verifyBot)
let botUserId = null;

// ID of the newest mention already seen: only newer mentions are fetched
let sinceId;
let firstCheck = true;

// Function to check if the bot is working
async function verifyBot() {
  try {
    // Get authenticated user information (X API v2)
    const me = await rwClient.v2.me();
    botUserId = me.data.id;
    console.log(`Bot successfully initialized! User: @${me.data.username}`);
    return true;
  } catch (error) {
    console.error('Error verifying bot:', error);
    return false;
  }
}

// Function to post a tweet
async function postTweet(text) {
  try {
    const tweet = await rwClient.v2.tweet(text);
    console.log(`Tweet posted successfully! ID: ${tweet.data.id}`);
    return tweet;
  } catch (error) {
    console.error('Error posting tweet:', error);
    throw error;
  }
}

// Function to respond to mentions
async function respondToMentions() {
  if (!botUserId) return;
  try {
    // Fetch mentions newer than sinceId (X API v2)
    const mentions = await rwClient.v2.userMentionTimeline(botUserId, {
      max_results: 10,
      since_id: sinceId,
    });

    // Remember the newest mention, so the next check skips the ones handled now
    sinceId = mentions.data?.meta?.newest_id ?? sinceId;

    // First check after start: only record the newest mention, so a restart doesn't reply to old ones again
    if (firstCheck) {
      firstCheck = false;
      return;
    }

    for (const tweet of mentions.data?.data ?? []) {
      if (tweet.text.includes('!ping')) {
        // Reply to the mention
        await rwClient.v2.reply(
          'Pong! 🤖 X Bot working correctly!',
          tweet.id
        );
        console.log(`Replied to mention (tweet ID: ${tweet.id})`);
      }
    }
  } catch (error) {
    console.error('Error processing mentions:', error);
  }
}

// Function to search and interact with specific tweets
async function searchAndInteract(query) {
  if (!botUserId) return;
  try {
    // Search recent tweets (X API v2)
    const tweets = await rwClient.v2.search(query, {
      max_results: 10,
    });

    for (const tweet of tweets.data?.data ?? []) {
      // Like the tweet
      await rwClient.v2.like(botUserId, tweet.id);
      console.log(`Liked tweet ID: ${tweet.id}`);

      // Wait a bit between actions to avoid rate limiting
      await new Promise(resolve => setTimeout(resolve, 2000));
    }
  } catch (error) {
    console.error('Error searching and interacting:', error);
  }
}

// Main bot function
async function runBot() {
  console.log('Starting X bot...');
  
  // Check if the bot is configured correctly
  const botOk = await verifyBot();
  if (!botOk) {
    console.error('Bot initialization failed');
    return;
  }
  
  // Record the newest existing mention, so only new mentions get replies
  await respondToMentions();
  
  // Main bot loop
  setInterval(async () => {
    try {
      // Check and respond to mentions every 5 minutes
      await respondToMentions();
      
      // Example: Search and interact with tweets about a specific topic
      // await searchAndInteract('#SquareCloud');
      
    } catch (error) {
      console.error('Error in main loop:', error);
    }
  }, 5 * 60 * 1000); // 5 minutes
  
  console.log('Bot running. Press Ctrl+C to stop.');
}

// Signal handling for graceful shutdown
process.on('SIGINT', () => {
  console.log('\nShutting down X bot...');
  process.exit(0);
});

process.on('SIGTERM', () => {
  console.log('\nShutting down X bot...');
  process.exit(0);
});

// Initialize the bot
runBot();
```

## Configure Square Cloud

Create a `squarecloud.app` file at the root of your project, next to `index.js`. It tells Square Cloud how to run your bot:

```systemd squarecloud.app theme={"system"}
DISPLAY_NAME=My X Bot
MAIN=index.js
MEMORY=512
VERSION=recommended
AUTORESTART=true
```

* `MEMORY` is the bot's RAM in MB. Bots need at least 256 MB; 512 MB leaves room to grow.
* `AUTORESTART=true` starts the bot again if it crashes. See [how auto restart works](https://help.squarecloud.app/en-us/article/how-to-use-auto-restart-on-square-cloud-nmhvh0/).
* There is no `SUBDOMAIN`: the bot calls the X API and doesn't serve web pages.

See the [configuration file reference](/en/getting-started/config-file) for every field.

## Set the credentials

<Note>**Security**: Never include your API credentials directly in code. Always use environment variables on Square Cloud.</Note>

The bot needs four environment variables:

* `API_KEY`: Your X API key
* `API_SECRET_KEY`: Your X API secret key
* `ACCESS_TOKEN`: Your access token
* `ACCESS_TOKEN_SECRET`: Your access token secret

Set them in one of these ways:

* **Dashboard**: on the upload page, open **Advanced configuration** and add the four variables. For a bot that is already deployed, add them on the application's **Environment Variables** page and restart the bot.
* **CLI**: right after you upload the bot (see [Deploy and verify](#deploy-and-verify)), load them from your local `.env` file with [`squarecloud app env set`](/en/cli-reference/environment-variables#squarecloud-app-env-set) and restart the bot:

```bash theme={"system"}
squarecloud app env set --from-file .env
squarecloud app restart
```

The bot reads its environment variables when it starts, so restart it after every change. See [Environment variables](/en/platform/environment-variables) for the other ways to manage them.

## What goes in the ZIP

* `index.js` and your other source files.
* `package.json`.
* `squarecloud.app`.

Leave out `node_modules`: Square Cloud installs your dependencies when the bot starts. Also leave out your `.env` file: if you upload with the CLI, add a line with `.env` to your [`squarecloud.ignore`](/en/getting-started/squarecloud-ignore) file so the CLI skips it.

## Deploy and verify

Create the ZIP described above, then upload it:

### Via dashboard

<Steps>
  <Step title="Access the Upload Page">
    Access the [upload page](https://squarecloud.app/en/dashboard/new) and upload your project zip file.
  </Step>

  <Step title="Configure Your Environment">
    After uploading your zip, you will need to configure the name, main file or runtime environment and other settings for your project.\
    If you are uploading a web project, make sure to select "Web Publication" and set a subdomain to your project.
  </Step>

  <Step title="Deploy Your Project">
    Finally, click on the "Deploy" button to host your project on Square Cloud.\
    After deployment, you can monitor your project's status and logs from the dashboard.

    <Frame>
      <img src="https://cdn.squarecloud.app/docs/articles/dashboard/uploading.gif" alt="Uploading application to Square Cloud" style={{ borderRadius: "0.2rem" }} />
    </Frame>
  </Step>

  <Step title="Confirm Your App Is Live">
    Your first deploy usually takes less than a minute. In the [dashboard](https://squarecloud.app/en/dashboard), wait for your application status to show as running and check the logs for any startup errors.\
    If you deployed a website or API, open `https://<your-subdomain>.squareweb.app` in your browser: you should see your application responding. If you deployed a bot, send it a command to confirm it is online.
    <Tip>App not starting? See the [Troubleshooting guide](/en/platform/troubleshooting/app-wont-start) for the most common causes and fixes.</Tip>
  </Step>
</Steps>

### Via CLI

To use this method, your project needs a configuration file named `squarecloud.app` at its root. It tells Square Cloud how to run your application.

<Card title="Configuration file guide" icon="link" href="/en/getting-started/config-file">
  Learn how to create the `squarecloud.app` configuration file that defines your application's environment.
</Card>

<Steps>
  <Step title="Install the CLI">
    Install the [Square Cloud CLI](/en/cli-reference/installation). If you already have it, run the same command to update it:

    <CodeGroup>
      ```bash macOS, Linux, and WSL theme={"system"}
      curl -fsSL https://cli.squarecloud.app/install | bash
      ```

      ```bash Windows with npm theme={"system"}
      npm install -g @squarecloud/cli@latest
      ```
    </CodeGroup>
  </Step>

  <Step title="Log In">
    Run the command below. It opens your browser: approve the login there and the CLI is ready. There is no API key to copy. For scripts and CI, see [CLI authentication](/en/cli-reference/authentication).

    ```bash theme={"system"}
    squarecloud auth login
    ```
  </Step>

  <Step title="Upload Your Project">
    From your project folder, run the command below. The CLI zips the current folder, leaving out what [`squarecloud.ignore`](/en/getting-started/squarecloud-ignore) lists, and uploads it:

    ```bash theme={"system"}
    squarecloud upload
    ```

    To upload a zip you created yourself, pass it with `--file`:

    ```bash theme={"system"}
    squarecloud upload --file <path/to/zip>
    ```
  </Step>

  <Step title="Confirm Your App Is Live">
    Your first deploy usually takes less than a minute. Check your application status and logs directly from the terminal:

    ```bash theme={"system"}
    squarecloud app status
    squarecloud app logs
    ```

    If you deployed a website or API, open `https://<your-subdomain>.squareweb.app` in your browser: you should see your application responding. If you deployed a bot, send it a command to confirm it is online.
    <Tip>App not starting? See the [Troubleshooting guide](/en/platform/troubleshooting/app-wont-start) for the most common causes and fixes.</Tip>
  </Step>
</Steps>

### Test the bot

After deployment, check the application logs in the Square Cloud dashboard for the `Bot successfully initialized!` message to confirm the bot authenticated correctly. Then, from another account, post a tweet that mentions the bot with `!ping` (for example, `@your_bot !ping`) and confirm it replies with `Pong! 🤖 X Bot working correctly!` on X within a few minutes. Mentions posted before the bot started are skipped.

## Common errors

### Request failed with code 401

The logs show `Error verifying bot:` followed by this error. One of the four credentials is wrong or missing. Copy them again from the "Keys and tokens" section, set the four variables and restart the bot.

### Request failed with code 403 when replying

The bot authenticates but can't post. The access tokens were generated while the app had read-only permission. Set "Read and write" in the app permissions, regenerate the Access Token and Secret, update `ACCESS_TOKEN` and `ACCESS_TOKEN_SECRET` and restart the bot.

### Request failed with code 429

The bot reached an X API rate limit. Wait before the next request, and space out your calls as in [Rate limiting](#rate-limiting).

## Going further

The snippets below extend `index.js`: add them to the file and call them where your bot needs them.

### Hashtag monitoring

```javascript theme={"system"}
// Function to monitor specific hashtags
async function monitorHashtags(hashtags) {
  for (const hashtag of hashtags) {
    try {
      const tweets = await rwClient.v2.search(`#${hashtag}`, {
        max_results: 10,
      });

      // Process found tweets
      for (const tweet of tweets.data?.data ?? []) {
        console.log(`Tweet found with #${hashtag}: ${tweet.text}`);
        // Implement interaction logic
      }
    } catch (error) {
      console.error(`Error monitoring #${hashtag}:`, error);
    }
  }
}
```

### Scheduled posts

```javascript theme={"system"}
// Function to schedule posts
function schedulePost(text, delay) {
  setTimeout(async () => {
    try {
      await postTweet(text);
      console.log('Scheduled post published successfully!');
    } catch (error) {
      console.error('Error publishing scheduled post:', error);
    }
  }, delay);
}

// Example: Schedule a post for 1 hour
schedulePost('🤖 Scheduled post by the bot!', 60 * 60 * 1000);
```

### Rate limiting

X (Twitter) has strict rate limits. Implement controls to avoid exceeding these limits:

```javascript theme={"system"}
// Rate limiting control
const rateLimiter = {
  lastRequest: 0,
  minInterval: 1000, // 1 second between requests
  
  async wait() {
    const now = Date.now();
    const timeSinceLastRequest = now - this.lastRequest;
    
    if (timeSinceLastRequest < this.minInterval) {
      const waitTime = this.minInterval - timeSinceLastRequest;
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
    
    this.lastRequest = Date.now();
  }
};
```

### Error handling

```javascript theme={"system"}
// Helper function to retry an operation on failure
async function retryOperation(operation, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      console.error(`Attempt ${i + 1} failed:`, error.message);
      
      if (i === maxRetries - 1) {
        throw error;
      }
      
      // Wait before retrying
      await new Promise(resolve => setTimeout(resolve, 2000 * (i + 1)));
    }
  }
}
```

## Next steps

<CardGroup cols={3}>
  <Card title="App won't start" icon="triangle-exclamation" href="/en/platform/troubleshooting/app-wont-start">
    Fix missing modules, memory errors and other startup failures.
  </Card>

  <Card title="Environment variables" icon="key" href="/en/platform/environment-variables">
    Manage your bot's credentials and other secrets.
  </Card>

  <Card title="Node.js runtime" icon="node-js" href="/en/articles/getting-started-with-nodejs">
    How Square Cloud runs Node.js applications.
  </Card>
</CardGroup>

For more about twitter-api-v2 and the X API, see the [official twitter-api-v2 documentation](https://github.com/PLhery/node-twitter-api-v2), the [official X API documentation](https://docs.x.com/overview), the [X API usage policies](https://developer.x.com/en/developer-terms/policy) and the [X API introduction](https://docs.x.com/x-api/getting-started/about-x-api).

## Contact us

If you continue facing **technical difficulties**, our **specialized support team** is available to assist you. [**Contact us**](https://squarecloud.app/en/support) and we'll be happy to help you resolve any issue: support quality is a big part of why developers rate Square Cloud [**4.9/5 across 402 reviews**](https://www.trustpilot.com/review/squarecloud.app) on Google and Trustpilot.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.