> ## 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.

# Get users (batch)

> Retrieve multiple user profiles by ID in a single request, up to 100 at a time

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

<Note>
  Requested result counts are upper bounds for paid authenticated calls. When remaining credits cannot cover the full page or ID list, Xquik returns fewer results. If zero paid results are affordable, it returns `402 insufficient_credits`.
</Note>

<Callout icon="coins" color="#5c3327">
  **1 credit per user returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit
</Callout>

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://xquik.com/api/v1/x/users/batch?ids=44196397,987654321" \
    -H "x-api-key: xq_YOUR_KEY_HERE" | jq
  ```

  ```javascript Node.js theme={null}
  const ids = ["44196397", "987654321"];
  const response = await fetch(
    `https://xquik.com/api/v1/x/users/batch?ids=${ids.join(",")}`,
    {
      headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
    }
  );
  const data = await response.json();
  const profilesById = new Map(data.users.map((user) => [user.id, user]));
  const profileRows = data.users.map((user) => ({
    user_id: user.id,
    username: user.username,
    display_name: user.name,
    bio: user.description ?? null,
    follower_count: user.followers ?? null,
    following_count: user.following ?? null,
    verified: user.verified ?? false,
    profile_image_url: user.profilePicture ?? null,
  }));
  const missingIds = ids.filter((id) => !profilesById.has(id));
  ```

  ```python Python theme={null}
  import requests

  ids = ["44196397", "987654321"]
  response = requests.get(
      "https://xquik.com/api/v1/x/users/batch",
      params={"ids": ",".join(ids)},
      headers={"x-api-key": "xq_YOUR_KEY_HERE"},
  )
  data = response.json()
  profiles_by_id = {user["id"]: user for user in data["users"]}
  profile_rows = [
      {
          "user_id": user["id"],
          "username": user["username"],
          "display_name": user["name"],
          "bio": user.get("description"),
          "follower_count": user.get("followers"),
          "following_count": user.get("following"),
          "verified": user.get("verified", False),
          "profile_image_url": user.get("profilePicture"),
      }
      for user in data["users"]
  ]
  missing_ids = [user_id for user_id in ids if user_id not in profiles_by_id]
  ```
</CodeGroup>

The Node.js and Python snippets shape durable profile rows instead of printing
full profile objects. Persist `profileRows` or `profile_rows` and `missingIds`
or `missing_ids` with the original ID list so retries only request missing
profiles.

## Direct batch user handoff

Use `GET /x/users/batch` when a CRM, warehouse, enrichment, lead scoring, or
agent workflow already has numeric X user IDs and needs profile details in one
JSON response. Use [Get User](/api-reference/x/get-user) when you have one ID
or username to resolve.

Store `requested_ids`, `user_id`, `username`, `display_name`, profile metrics,
verification state, `profile_image_url`, `has_next_page`, and `next_cursor`.
Join returned users by `user_id` instead of relying on response order. Send at
most 100 IDs per request. Batch requests always return `has_next_page: false`
and `next_cursor: ""`; zero affordable results return
`402 insufficient_credits`.

<CardGroup cols={2}>
  <Card title="Known IDs" icon="key-round">
    Send comma-separated X user IDs in `ids`. Keep the original list as
    `requested_ids` for retry and audit rows.
  </Card>

  <Card title="Profile rows" icon="user-round">
    Store each returned `id` as `user_id` with `username`, `name`,
    `description`, `followers`, `following`, `verified`, and `verifiedType`.
  </Card>

  <Card title="Missing rows" icon="triangle-alert">
    Compare returned `user_id` values with `requested_ids`. Retry only the
    missing IDs or route username-only inputs to Get User first.
  </Card>

  <Card title="No pagination" icon="route">
    Use the single-page batch contract: `has_next_page: false` and
    `next_cursor: ""`. Do not treat it as a cursor workflow.
  </Card>
</CardGroup>

## Which lookup endpoint?

<CardGroup cols={2}>
  <Card title="One profile" icon="user-round">
    Use [`GET /x/users/{id}`](/api-reference/x/get-user) for one username or one
    user ID.
  </Card>

  <Card title="Many known IDs" icon="key-round">
    Use `GET /x/users/batch` for up to 100 comma-separated user IDs in one
    request.
  </Card>

  <Card title="Name or partial handle" icon="search">
    Use [`GET /x/users/search`](/api-reference/x/search-users) before batch
    lookup when the workflow starts from a name, brand, or handle fragment.
  </Card>

  <Card title="Tweet IDs" icon="list">
    Use [`GET /x/tweets/batch`](/api-reference/x/batch-tweets) when the input
    list contains tweet IDs instead of user IDs.
  </Card>

  <Card title="Audience pages" icon="users">
    Use [`GET /x/users/{id}/followers`](/api-reference/x/followers) or
    [`GET /x/users/{id}/following`](/api-reference/x/following) when the job
    starts from an account audience.
  </Card>

  <Card title="Saved exports" icon="file-spreadsheet">
    Use [`Create extraction`](/api-reference/extractions/create) when the source
    is a follower, following, timeline, media, or search job instead of an
    existing ID list.
  </Card>
</CardGroup>

## Query parameters

<ParamField query="ids" type="string" required>
  Comma-separated user IDs. Maximum 100 per request.
</ParamField>

## Headers

<ParamField header="x-api-key" type="string" required>
  Your API key. Session cookie authentication is also supported.
</ParamField>

## Response

<Tabs>
  <Tab title="200 OK">
    <ResponseField name="users" type="object[]">
      Array of user profiles matching the requested IDs.

      <Expandable title="User object fields">
        <ResponseField name="id" type="string">X user ID.</ResponseField>
        <ResponseField name="username" type="string">X username.</ResponseField>
        <ResponseField name="name" type="string">Display name.</ResponseField>
        <ResponseField name="description" type="string">Profile bio.</ResponseField>
        <ResponseField name="followers" type="number">Follower count.</ResponseField>
        <ResponseField name="following" type="number">Following count.</ResponseField>
        <ResponseField name="verified" type="boolean">Verified status.</ResponseField>
        <ResponseField name="profilePicture" type="string">Profile image URL.</ResponseField>
        <ResponseField name="location" type="string">Profile location.</ResponseField>
        <ResponseField name="createdAt" type="string">Account creation date (ISO 8601).</ResponseField>
        <ResponseField name="statusesCount" type="number">Total number of tweets posted. Omitted if unavailable.</ResponseField>
        <ResponseField name="coverPicture" type="string">Cover/banner image URL. Omitted if unavailable.</ResponseField>
        <ResponseField name="mediaCount" type="number">Total number of media tweets posted. Omitted if unavailable.</ResponseField>
        <ResponseField name="canDm" type="boolean">Whether the user accepts direct messages. Omitted if unavailable.</ResponseField>
        <ResponseField name="url" type="string">Website URL from profile. Omitted if empty.</ResponseField>
        <ResponseField name="favouritesCount" type="number">Total number of tweets liked. Omitted if unavailable.</ResponseField>
        <ResponseField name="hasCustomTimelines" type="boolean">Whether the user has custom timelines. Omitted if unavailable.</ResponseField>
        <ResponseField name="isTranslator" type="boolean">Whether the user is an X translator. Omitted if unavailable.</ResponseField>
        <ResponseField name="withheldInCountries" type="string[]">Country codes where the account is withheld. Omitted if empty.</ResponseField>
        <ResponseField name="possiblySensitive" type="boolean">Whether the account is flagged as possibly sensitive. Omitted if unavailable.</ResponseField>
        <ResponseField name="pinnedTweetIds" type="string[]">IDs of pinned tweets. Omitted if none.</ResponseField>
        <ResponseField name="isAutomated" type="boolean">Whether the account is marked as automated. Omitted if unavailable.</ResponseField>
        <ResponseField name="automatedBy" type="string">Username of the account operator if automated. Omitted if not automated.</ResponseField>
        <ResponseField name="unavailable" type="boolean">Whether the account is unavailable. Omitted if available.</ResponseField>
        <ResponseField name="unavailableReason" type="string">Reason the account is unavailable. Omitted if available.</ResponseField>
        <ResponseField name="verifiedType" type="string">Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check.</ResponseField>
        <ResponseField name="profile_bio" type="object">Structured profile bio with entity annotations. Omitted if unavailable.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="has_next_page" type="boolean">Always `false` for batch requests.</ResponseField>
    <ResponseField name="next_cursor" type="string">Always empty for batch requests.</ResponseField>

    ```json theme={null}
    {
      "users": [
        {
          "id": "44196397",
          "username": "elonmusk",
          "name": "Elon Musk",
          "followers": 200000000,
          "verified": true
        }
      ],
      "has_next_page": false,
      "next_cursor": ""
    }
    ```
  </Tab>

  <Tab title="400 Missing IDs">
    ```json theme={null}
    { "error": "missing_ids", "message": "ids parameter required" }
    ```
  </Tab>

  <Tab title="400 Too many IDs">
    ```json theme={null}
    { "error": "too_many_ids", "message": "Max 100 IDs per request" }
    ```
  </Tab>

  <Tab title="401 Unauthenticated">
    ```json theme={null}
    { "error": "unauthenticated" }
    ```
  </Tab>

  <Tab title="402 Subscription required">
    ```json theme={null}
    { "error": "no_subscription" }
    ```

    No active subscription or insufficient credits. Possible error values: `no_subscription`, `subscription_inactive`, `no_credits`, `insufficient_credits`.
  </Tab>

  <Tab title="502 X API unavailable">
    ```json theme={null}
    { "error": "x_api_unavailable" }
    ```

    The read service returned an error. Retry after a short delay.
  </Tab>

  <Tab title="429 Rate Limit Exceeded">
    ```json theme={null}
    { "error": "rate_limit_exceeded", "retryAfter": 60 }
    ```

    Your tier rate limit was exceeded. Wait for the `Retry-After` header before retrying.
  </Tab>

  <Tab title="424 Dependency Failed">
    ```json theme={null}
    { "error": "x_api_unavailable" }
    ```

    The normalized v1 response contract can return 424 when the read service is unavailable.
  </Tab>
</Tabs>

<Note>
  **Related:** [Batch Tweets](/api-reference/x/batch-tweets) · [Get User](/api-reference/x/get-user)
</Note>
