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

# Twitter extraction status & result progress

> Inspect an extraction job's status, tool, progress, row count, and paginated tweet, follower, reply, profile, timeline, or list results. Polling is free.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="200" id="response-extractions-twitter-extraction-results-200">
      ```json theme={null}
      {
        "job": {
          "id": "id",
          "toolType": "follower_explorer",
          "status": "completed",
          "totalResults": 0,
          "createdAt": "2026-08-23T10:00:00Z"
        },
        "results": [],
        "hasMore": false,
        "pollAfterMs": 0,
        "waitUrl": "/api/v1/extractions/id?wait=10&limit=1&outputMode=compact"
      }
      ```
    </Tab>

    <Tab title="400" id="response-extractions-twitter-extraction-results-400">
      ```json theme={null}
      {
        "error": "invalid_input",
        "message": "Invalid input. Check the request body."
      }
      ```
    </Tab>

    <Tab title="401" id="response-extractions-twitter-extraction-results-401">
      ```json theme={null}
      {
        "error": "unauthenticated",
        "message": "Authentication required. Provide a valid API key or bearer token."
      }
      ```
    </Tab>

    <Tab title="404" id="response-extractions-twitter-extraction-results-404">
      ```json theme={null}
      {
        "error": "not_found",
        "message": "Resource not found."
      }
      ```
    </Tab>

    <Tab title="429" id="response-extractions-twitter-extraction-results-429">
      ```json theme={null}
      {
        "error": "rate_limit_exceeded",
        "message": "Too many requests. Try again later.",
        "retryAfter": 60
      }
      ```
    </Tab>
  </Tabs>
</Panel>

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

<Callout icon="circle-check" color="#16a34a">
  **Free.** This endpoint does not consume credits.
</Callout>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890?limit=100" \
    -H "x-api-key: xq_YOUR_KEY_HERE" | jq
  ```

  ```javascript Node.js theme={null}
  const extractionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
  const params = new URLSearchParams({ limit: "100" });

  const response = await fetch(`https://xquik.com/api/v1/extractions/${extractionId}?${params}`, {
    method: "GET",
    headers: {
      "x-api-key": "xq_YOUR_KEY_HERE",
    },
  });
  const data = await response.json();
  ```

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

  extraction_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  response = requests.get(
      f"https://xquik.com/api/v1/extractions/{extraction_id}",
      headers={"x-api-key": "xq_YOUR_KEY_HERE"},
      params={"limit": 100},
  )
  data = response.json()
  ```

  ```go Go theme={null}
  package main

  import (
      "encoding/json"
      "fmt"
      "net/http"
  )

  func main() {
      req, err := http.NewRequest("GET", "https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890?limit=100", nil)
      if err != nil {
          panic(err)
      }
      req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE")

      resp, err := http.DefaultClient.Do(req)
      if err != nil {
          panic(err)
      }
      defer resp.Body.Close()

      var data map[string]interface{}
      if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
          panic(err)
      }
      fmt.Println(data)
  }
  ```
</CodeGroup>

## Headers

<ParamField header="x-api-key" type="string" required>
  Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard).
</ParamField>

## Path parameters

<ParamField path="id" type="string" required>
  Extraction job ID returned from [Create Extraction](/api-reference/extractions/create) or [List Extractions](/api-reference/extractions/twitter-scraping-job-history).
</ParamField>

## Query parameters

<ParamField query="limit" type="number">
  Number of results to return per page. Default and maximum: `1000`.
</ParamField>

<ParamField query="cursor" type="string">
  Pass the previous `nextCursor` unchanged. Do not send `offset`. It returns `400`.
</ParamField>

<ParamField query="wait" type="number">
  Wait up to 10 seconds when the job is active. Defaults to `0`.
</ParamField>

<ParamField query="outputMode" type="string">
  Use `compact` for core fields and tweet counts. Use `full` for nested enrichment. Use `raw` for a source copy. Defaults to `full`.
</ParamField>

<ParamField query="outputPreset" type="string">
  Keep enrichment `nested` or merge it with `flat`.
</ParamField>

<ParamField query="fieldStyle" type="string">
  Field names: `source`, `camelCase`, or `snake_case`.
</ParamField>

<ParamField query="includeRaw" type="boolean">
  Deprecated. Use `outputMode=raw`.
</ParamField>

| Extraction result page column | Response source | Export rule |
| - | - | - |
| Job ID | `job.id` | Keep this ID with every exported row. |
| Extraction tool | `job.toolType` | Record which tool produced the rows. |
| Job state | `job.status` | Export final rows only after completion. |
| Total rows | `job.totalResults` | Compare this count with all saved pages. |
| Result ID | `results[].id` | Use this ID for result-page deduplication. |
| X user ID | `results[].xUserId` | Join profile records on this ID. It does not change. |
| Tweet fields | `tweetId`, `tweetText`, and `tweetCreatedAt` | Store them only when returned. |
| More pages | `hasMore` | Continue only while this value is `true`. |
| Next page | `nextCursor` | Pass this value as the next `cursor` parameter. |

## Response

### 200 OK

<ResponseField name="job" type="object">
  Extraction job metadata.
  **Job object fields.**

  <ResponseField name="id" type="string">
    Unique extraction job ID.
  </ResponseField>

  <ResponseField name="toolType" type="string">
    Tool type used for this extraction.
  </ResponseField>

  <ResponseField name="status" type="string">
    Job status: `completed`, `failed`, or `running`.
  </ResponseField>

  <ResponseField name="totalResults" type="number">
    Total number of extracted results.
  </ResponseField>

  <ResponseField name="targetTweetId" type="string">
    Target tweet ID. Present for tweet-based tools.
  </ResponseField>

  <ResponseField name="targetUsername" type="string">
    Target username. Present for user-based tools.
  </ResponseField>

  <ResponseField name="targetUserId" type="string">
    Target X user ID. Present for user-based tools.
  </ResponseField>

  <ResponseField name="targetCommunityId" type="string">
    Target community ID. Present for `community_extractor`, `community_moderator_explorer`,
    `community_post_extractor`, `community_search`.
  </ResponseField>

  <ResponseField name="searchQuery" type="string">
    Search query. Present for `people_search`, `community_search`.
  </ResponseField>

  <ResponseField name="errorMessage" type="string">
    Error description. Only present for failed jobs. `errorXAccountRequired` means the tool needs your
    connected X account.
  </ResponseField>

  <ResponseField name="createdAt" type="string">
    ISO 8601 timestamp of when the job was created.
  </ResponseField>

  <ResponseField name="completedAt" type="string">
    ISO 8601 timestamp of when the job finished. Only present for completed jobs.
  </ResponseField>
</ResponseField>

<ResponseField name="results" type="object[]">
  Array of extracted user/tweet records for the current page.

  <Info>
    Every result includes `id` and `xUserId`. The API omits all other fields when unavailable and never sets them to `null`. Check that a field is present before accessing it.
  </Info>

  **Result object fields.**

  <ResponseField name="id" type="string" required>
    Unique result ID.
  </ResponseField>

  <ResponseField name="xUserId" type="string" required>
    X user ID.
  </ResponseField>

  <ResponseField name="xUsername" type="string">
    X username (handle). Omitted if unavailable.
  </ResponseField>

  <ResponseField name="xDisplayName" type="string">
    X display name. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="xFollowersCount" type="number">
    Follower count at time of extraction. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="xVerified" type="boolean">
    Whether the user has a verified badge. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="xProfileImageUrl" type="string">
    URL to the user's profile image. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="tweetId" type="string">
    Tweet ID. Omitted for non-tweet-based extractions.
  </ResponseField>

  <ResponseField name="tweetUrl" type="string">
    Canonical X post URL. Omitted for non-tweet-based extractions.
  </ResponseField>

  <ResponseField name="tweetText" type="string">
    Tweet text content. Omitted for non-tweet-based extractions.
  </ResponseField>

  <ResponseField name="tweetCreatedAt" type="string">
    ISO 8601 timestamp of the tweet. Omitted for non-tweet-based extractions.
  </ResponseField>

  <ResponseField name="media" type="object[]">
    Attached media with `outputPreset=flat`. Full nested output stores it at
    `enrichmentData.tweet.media`.
  </ResponseField>

  <ResponseField name="likeCount" type="number">
    Likes. Omitted when unavailable.
  </ResponseField>

  <ResponseField name="retweetCount" type="number">
    Reposts. Omitted when unavailable.
  </ResponseField>

  <ResponseField name="replyCount" type="number">
    Replies. Omitted when unavailable.
  </ResponseField>

  <ResponseField name="quoteCount" type="number">
    Quotes. Omitted when unavailable.
  </ResponseField>

  <ResponseField name="viewCount" type="number">
    Views. Omitted when unavailable.
  </ResponseField>

  <ResponseField name="bookmarkCount" type="number">
    Bookmarks. Omitted when unavailable.
  </ResponseField>

  <ResponseField name="createdAt" type="string">
    ISO 8601 timestamp of when the result was created.
  </ResponseField>

  <ResponseField name="enrichmentData" type="object">
    Additional profile, Tweet, media, and article metadata. Compact output omits this object. Full
    output keeps it nested. Flat output merges its fields into the result.
  </ResponseField>

  <ResponseField name="enrichmentData.profile" type="object">
    Full profile, as `GET /x/users/{id}` returns it. Present when the job sets `enrichProfiles` for
    `follower_explorer`, `following_explorer`, `verified_follower_explorer`, `people_search` or
    `repost_extractor`. Omitted when the profile read fails. Flat output keeps it as a nested
    `profile` object.
  </ResponseField>
</ResponseField>

<ResponseField name="hasMore" type="boolean">Whether more results exist beyond this page.</ResponseField>
<ResponseField name="pollAfterMs" type="number">Recommended delay before another immediate check.</ResponseField>
<ResponseField name="waitUrl" type="string">Relative polling URL. It waits 10 seconds and returns 1 compact row.</ResponseField>
<ResponseField name="nextCursor" type="string">Cursor for the next `cursor` parameter. Only present when `hasMore` is `true`.</ResponseField>

With `outputMode=compact`, tweet results keep `tweetUrl`, `likeCount`,
`retweetCount`, `replyCount`, `quoteCount`, `viewCount`, and `bookmarkCount`.
Compact output omits `xProfileImageUrl` and `enrichmentData`.

### 401 Unauthenticated

```json theme={null}
{ "error": "unauthenticated" }
```

Missing or invalid API key.

### 404 Not found

```json theme={null}
{ "error": "not_found" }
```

No extraction job exists with this ID, or it belongs to a different account.

### 429 Rate limited

```json theme={null}
{
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Try again later.",
  "retryAfter": 1
}
```

Wait for the `Retry-After` value before polling again.

## Paginating results

The API orders results by ID ascending. To iterate through all results for a large extraction:

1. Make the initial request without the `cursor` parameter.
2. Check `hasMore`. Pass `nextCursor` as the next `cursor` value.
3. Repeat until `hasMore` is `false`.

```javascript theme={null}
import { appendFile } from "node:fs/promises";
const extractionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
let cursor = undefined;
let pageIndex = 0;

do {
  const params = new URLSearchParams({ limit: "1000" });
  if (cursor) params.set("cursor", cursor);
  const pageCursor = cursor ?? null;

  const response = await fetch(`https://xquik.com/api/v1/extractions/${extractionId}?${params}`, {
    headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
  });
  const data = await response.json();

  const rows = data.results.map((result) => ({
    extraction_id: extractionId,
    row_id: result.id,
    x_user_id: result.xUserId,
    x_username: result.xUsername,
    tweet_id: result.tweetId,
    tweet_text: result.tweetText,
    page_index: pageIndex,
    page_cursor: pageCursor,
    next_cursor: data.nextCursor ?? null,
    has_more: data.hasMore,
    handoff_format: "jsonl",
  }));

  if (rows.length > 0) {
    await appendFile(
      "xquik-extraction-results.jsonl",
      `${rows.map((row) => JSON.stringify(row)).join("\n")}\n`,
    );
  }

  cursor = data.hasMore ? data.nextCursor : undefined;
  pageIndex += 1;
} while (cursor);
```

## Cursor handoff

Use `GET /extractions/{id}` when an integration needs structured JSON rows,
incremental checkpoints, or more rows than a file export can return. Treat
`nextCursor` as an opaque checkpoint and pass it back as `cursor`.

<CardGroup cols={2}>
  <Card title="Page checkpoint" icon="bookmark">
    Store `extraction_id`, `page_index`, `page_cursor`, `next_cursor`,
    `has_more`, and `result_count` after each successful page.
  </Card>

  <Card title="Row shape" icon="braces">
    Store selected fields such as `row_id`, `x_user_id`, `x_username`,
    `tweet_id`, and `tweet_text`. Do not write raw result arrays to shared logs.
  </Card>

  <Card title="Large jobs" icon="list-tree">
    Stream pages to `xquik-extraction-results.jsonl` when you need replayable
    rows or results beyond the export row cap.
  </Card>

  <Card title="File handoff" icon="download">
    Use [Export Extraction](/api-reference/extractions/export) when CSV, JSON,
    XLSX, Markdown, PDF, or TXT files are enough.
  </Card>
</CardGroup>

Store page checkpoints separately from row data:

```json theme={null}
{
  "extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "page_index": 3,
  "page_cursor": "990100",
  "next_cursor": "990200",
  "has_more": true,
  "result_count": 1000,
  "handoff_format": "jsonl"
}
```

<Note>
  **Next steps.** [Export Extraction](/api-reference/extractions/export) to download all results as CSV, XLSX, or Markdown, or [List Extractions](/api-reference/extractions/twitter-scraping-job-history) to browse your job history.
</Note>


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