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

# Make

> Build a private Make custom app for Xquik with API-key auth, search, trends, extraction jobs, monitor webhooks, and X actions

<blockquote className="agent-llms-directive">
  For the complete documentation index, see <a href="/llms.txt">llms.txt</a>.
</blockquote>

Use Xquik in Make when a scenario needs X (Twitter) search, account lookups, trends, publishing, extraction jobs, monitors, or webhook automation without an X Developer app.

Make discontinued its native X app in 2025. Start with a private Make custom app so your team can keep scenarios focused, test auth safely, and decide later whether to request public app review.

## Prerequisites

* [Xquik API key](/quickstart)
* Make organization with Custom Apps access
* HTTPS Make webhook URL for instant monitor-event scenarios
* Optional Slack, Sheets, Airtable, or CRM module for downstream steps

## App Shape

<CardGroup cols={2}>
  <Card title="Connection" icon="key-round">
    Use an API key parameter named `apiKey` and inject it as `x-api-key`.
  </Card>

  <Card title="Base URL" icon="link">
    Call Xquik REST modules from `https://xquik.com/api/v1`.
  </Card>

  <Card title="Modules" icon="boxes">
    Start with Search Tweets, Get Tweet, Get User, Get Trends, Create Tweet, Create Extraction, Create Monitor, Create Webhook, and Make an API Call.
  </Card>

  <Card title="Triggers" icon="radio">
    Support Monitor Event instant webhooks and Extraction Completed polling.
  </Card>

  <Card title="Error handling" icon="circle-alert">
    Map `401`, `402`, `429`, and `5xx` to short scenario messages.
  </Card>
</CardGroup>

Make custom apps split this into a connection, base request settings, modules, and optional webhook components. Use Xquik's `/account` endpoint as the connection test because it validates the API key without mutating data.

## Connection

Create a connection parameter that stores the API key as a password field:

```json theme={null}
[
  {
    "name": "apiKey",
    "label": "Xquik API Key",
    "type": "password",
    "required": true,
    "editable": true,
    "help": "Create an API key in the Xquik dashboard."
  }
]
```

Use `GET /account` to validate the connection:

```json theme={null}
{
  "url": "https://xquik.com/api/v1/account",
  "method": "GET",
  "headers": {
    "x-api-key": "{{parameters.apiKey}}"
  },
  "response": {
    "metadata": {
      "type": "email",
      "value": "{{body.email}}"
    },
    "error": {
      "message": "{{body.message || body.error || 'Xquik authentication failed.'}}"
    }
  },
  "log": {
    "sanitize": ["request.headers.x-api-key"]
  }
}
```

## Base Request Pattern

Use one base request pattern for JSON modules:

```json theme={null}
{
  "baseUrl": "https://xquik.com/api/v1",
  "headers": {
    "x-api-key": "{{connection.apiKey}}",
    "content-type": "application/json"
  },
  "response": {
    "error": {
      "message": "{{body.message || body.error || 'Xquik request failed.'}}"
    }
  },
  "log": {
    "sanitize": ["request.headers.x-api-key"]
  }
}
```

Handle status codes consistently:

<CardGroup cols={2}>
  <Card title="401 authentication" icon="key-round">
    Authentication failed. Check the Xquik API key.
  </Card>

  <Card title="402 billing state" icon="credit-card">
    Subscription or credits required. Update billing in Xquik.
  </Card>

  <Card title="429 rate limit" icon="timer">
    Rate limited. Respect the `Retry-After` header before retrying.
  </Card>

  <Card title="5xx transient" icon="refresh-cw">
    Xquik service unavailable. Retry with exponential backoff.
  </Card>
</CardGroup>

## Starter Modules

<CardGroup cols={3}>
  <Card title="Search Tweets" icon="search">
    Search module. Call `GET /x/tweets/search` with `q`; use `cursor` for page loops and keep `limit` on bounded resumes.
  </Card>

  <Card title="Get Tweet" icon="message-circle">
    Action module. Call `GET /x/tweets/{id}` with a tweet ID.
  </Card>

  <Card title="Get User" icon="user-round">
    Action module. Call `GET /x/users/{id}` with a user ID or username.
  </Card>

  <Card title="Get Trends" icon="trending-up">
    Search module. Call `GET /x/trends` with optional `woeid` and `count`.
  </Card>

  <Card title="Create Tweet" icon="send">
    Action module. Call `POST /x/tweets` with account, text, and optional public media URLs.
  </Card>

  <Card title="Create Extraction" icon="boxes">
    Action module. Call `POST /extractions` with `toolType`, query fields, and result limit.
  </Card>

  <Card title="Create Monitor" icon="radio">
    Action module. Call `POST /monitors` with username and event types.
  </Card>

  <Card title="Create Webhook" icon="webhook">
    Action module. Call `POST /webhooks` with callback URL and event types.
  </Card>

  <Card title="Make an API Call" icon="terminal">
    Universal module. Accept any `/api/v1` path as an escape hatch for endpoints not yet modeled.
  </Card>
</CardGroup>

Example Search Tweets module communication:

```json theme={null}
{
  "url": "/x/tweets/search",
  "method": "GET",
  "qs": {
    "q": "{{parameters.q}}",
    "cursor": "{{parameters.cursor}}"
  },
  "response": {
    "iterate": "{{body.tweets}}",
    "output": {
      "id": "{{item.id}}",
      "text": "{{item.text}}",
      "authorUsername": "{{item.author.username}}",
      "url": "{{item.url}}"
    }
  }
}
```

Add a bounded-pull variant that sends `limit`. If `body.has_next_page` is `true`, send `body.next_cursor` as `cursor` with the same `q`, filters, and `limit`.

## Output Handoff

Make response handling lets search modules `iterate` over `body.tweets` while `body` stays available for output, wrapper, and pagination fields. Emit tweet bundles from `item`, then carry setup IDs, write status, and page cursors in scenario state when downstream modules need another request. Use snake\_case keys for data-store rows even when the API response uses camelCase.

<CardGroup cols={2}>
  <Card title="Tweet search page" icon="search">
    Store `q`, each `tweet_id`, `text`, `author_username`, `created_at`, `has_next_page`, and `next_cursor`.
  </Card>

  <Card title="User profile rows" icon="users">
    Store source `id` as `user_id`, plus `username`, `name`, `followers`, `verified`, and `profile_picture`. For user-list modules, carry `has_next_page` and `next_cursor`.
  </Card>

  <Card title="Trend rows" icon="trending-up">
    Store each trend `name`, `rank`, `query`, and `description`; keep `body.count`, `body.woeid`, and the selected region in scenario state.
  </Card>

  <Card title="Tweet or reply write" icon="send">
    Send a unique `Idempotency-Key`. Store the returned action. Poll `status_url` while `terminal` is false. Retry only when `safe_to_retry` is true, using a new key.
  </Card>

  <Card title="Media attachments" icon="image">
    For tweets or replies, pass public URLs in `media` and store `tweet_id` or `write_action_id`. For DMs, upload first, pass one `media_id` in `media_ids`, store `message_id`, and leave `reply_to_message_id` unset.
  </Card>

  <Card title="Monitor and webhook setup" icon="radio">
    Store monitor `id`, `username`, `xUserId`, `eventTypes`, `isActive`, `nextBillingAt`, webhook `id`, `url`, `eventTypes`, and the one-time `secret`. For Make storage rows, map production `deliveryId` to `delivery_id` for receiver retry de-dupe and `streamEventId` to `stream_event_id` when one monitor event should process once across endpoint changes.
  </Card>

  <Card title="Extraction jobs" icon="database">
    Store `id`, `tool_type`, and `status` from `POST /extractions`; poll `GET /extractions/{id}`, then carry `has_more` and `next_cursor`.
  </Card>

  <Card title="Webhook event dedupe" icon="fingerprint">
    Store `deliveryId` for endpoint-level retry dedupe and `streamEventId` when one monitor event must process once across receiver changes.
  </Card>

  <Card title="Stored Event Replay" icon="activity">
    Call `GET /api/v1/events` with `after` when a scenario needs replay. Map `id`, `monitorId`, `monitorType`, `occurredAt`, `hasMore`, and `nextCursor` to `event_id`, `monitor_id`, `monitor_type`, `occurred_at`, `has_more`, and `next_cursor`.
  </Card>

  <Card title="Receiver acceptance" icon="copy-check">
    Return `2xx` after accepting duplicate `deliveryId` or `streamEventId`; keep endpoint signing values, raw request body, raw signature, and full headers out of scenario logs, data stores, Slack messages, CRM rows, and retry queues.
  </Card>
</CardGroup>

## Instant Trigger: Monitor Events

Use a dedicated Make webhook for monitor events. Register that webhook URL in Xquik:

```json theme={null}
{
  "url": "https://xquik.com/api/v1/webhooks",
  "method": "POST",
  "body": {
    "url": "{{webhook.url}}",
    "eventTypes": ["tweet.new", "tweet.reply", "tweet.quote", "tweet.retweet"]
  }
}
```

Then create or confirm the monitor:

```json theme={null}
{
  "url": "https://xquik.com/api/v1/monitors",
  "method": "POST",
  "body": {
    "username": "username",
    "eventTypes": ["tweet.new", "tweet.reply", "tweet.quote", "tweet.retweet"]
  }
}
```

Map webhook output fields for downstream modules:

<CardGroup cols={3}>
  <Card title="Event type" icon="bell">
    Map `eventType` to route `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet` events.
  </Card>

  <Card title="Delivery ID" icon="fingerprint">
    Map `deliveryId` as the per-endpoint idempotency key for retries.
  </Card>

  <Card title="Stream event ID" icon="link">
    Map `streamEventId` when one monitor event should process once across endpoint changes.
  </Card>

  <Card title="Occurred at" icon="calendar-clock">
    Map `occurredAt` as the event timestamp.
  </Card>

  <Card title="Username" icon="at-sign">
    Map `username` for account monitor events.
  </Card>

  <Card title="Tweet ID" icon="hash">
    Map `data.id` as the tweet identifier.
  </Card>

  <Card title="Text" icon="type">
    Map `data.text` as the tweet body.
  </Card>

  <Card title="Author username" icon="user-round">
    Map `data.author.userName` when present. Use `username` as the monitored-account fallback.
  </Card>
</CardGroup>

Keep the webhook secret returned by Xquik. If the scenario includes a verification step before routing, verify `x-xquik-signature` with that secret before sending alerts.

## Polling Trigger: Extraction Completed

Use a polling trigger when users want bulk results without webhook setup:

```json theme={null}
{
  "url": "/extractions",
  "method": "GET",
  "qs": {
    "status": "completed",
    "limit": 25
  },
  "response": {
    "iterate": "{{body.extractions}}",
    "uid": "{{item.id}}",
    "output": {
      "id": "{{item.id}}",
      "status": "{{item.status}}",
      "toolType": "{{item.toolType}}",
      "createdAt": "{{item.createdAt}}",
      "completedAt": "{{item.completedAt}}"
    }
  }
}
```

Fetch the job detail with `GET /extractions/{id}` and loop through `nextCursor` when `hasMore` is true.

## Recipes

### Social Listening To Slack

<CardGroup cols={2}>
  <Card title="Monitor Event Trigger" icon="radio">
    Start from the Xquik Monitor Event instant trigger for `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet`.
  </Card>

  <Card title="Topic Filter" icon="funnel">
    Filter on `eventType`, `username`, and `data.text` before routing alerts.
  </Card>

  <Card title="Slack Message" icon="message-square">
    Create a Slack message from `data.text`, `data.id`, `data.author.userName`, and `occurredAt`.
  </Card>

  <Card title="Dedupe Store" icon="database">
    Upsert by `deliveryId` per endpoint. Use `streamEventId` when one monitor event should fan out once across endpoint changes.
  </Card>
</CardGroup>

### Daily Topic Research To Sheets

<CardGroup cols={2}>
  <Card title="Schedule Trigger" icon="calendar-clock">
    Run the scenario on a daily schedule for repeatable topic research.
  </Card>

  <Card title="Search Tweets" icon="search">
    Call Xquik Search Tweets with `q`; use `cursor` for page loops and keep `limit` on bounded resumes.
  </Card>

  <Card title="Iterator" icon="list">
    Iterate over `tweets` and pass one tweet bundle to each downstream module.
  </Card>

  <Card title="Sheet Row" icon="table">
    Append `id`, `author.username`, `text`, `createdAt`, `likeCount`, and `retweetCount`.
  </Card>
</CardGroup>

### Bulk Extraction To CRM

<CardGroup cols={3}>
  <Card title="Start Run" icon="play">
    Use a scheduler or manual trigger to start the bulk extraction.
  </Card>

  <Card title="Create Extraction" icon="boxes">
    Call Xquik Create Extraction with `toolType` and the required target fields.
  </Card>

  <Card title="Wait or Poll" icon="timer">
    Wait before polling, or reuse the Extraction Completed polling trigger.
  </Card>

  <Card title="Get Extraction" icon="search">
    Call `GET /extractions/{id}` until `job.status` is `completed` or `failed`.
  </Card>

  <Card title="CRM Upsert" icon="database">
    Upsert by user `id`, then follow `hasMore` and `nextCursor` for additional result pages.
  </Card>
</CardGroup>

## Test Checklist

* Connection test rejects invalid API keys with a clear `401` message.
* Every module sanitizes `x-api-key` in logs.
* Search modules return arrays and stable IDs for Make deduplication.
* Instant trigger maps `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet`.
* Extraction polling stops when no new completed jobs are returned.
* `429` errors tell users to wait for `Retry-After`.
* The Universal module accepts any `/api/v1` path but still injects the API key.

## Next Steps

* Read [Webhooks](/webhooks/overview) for payload shape and retries.
* Read [Extraction Workflow](/guides/extraction-workflow) for job creation and pagination.
* Use [Zapier](/guides/zapier) or [Pipedream](/guides/pipedream) when the team prefers code-backed workflow components.
