# Xquik REST API Endpoints

Base URL: `https://xquik.com/api/v1`

All requests require the `x-api-key` header. All responses are JSON. HTTPS only.

## Table of Contents

- [Account](#account)
- [API Keys](#api-keys)
- [Monitors](#monitors)
- [Events](#events)
- [Webhooks](#webhooks)
- [Draws](#draws)
- [Extractions](#extractions)
- [X API (Direct Lookups)](#x-api-direct-lookups)
- [X Media (Download)](#x-media-download)
- [Trends](#trends)
- [Radar](#radar)
- [Compose](#compose)
- [Drafts](#drafts)
- [Tweet Style Cache](#tweet-style-cache)
- [Account Identity](#account-identity)
- [Subscribe](#subscribe)
- [X Accounts (Connected)](#x-accounts-connected)
- [X Write](#x-write)
- [Credits](#credits)
- [Support](#support)

---

## Account

### Get Account

```
GET /account
```

Returns account status, monitor billing details, credit balance, and X identity.

**Response:**
```json
{
  "plan": "active",
  "monitorsAllowed": 9007199254740991,
  "monitorsUsed": 1,
  "monitorBilling": {
    "activeDailyEstimate": "500",
    "activeHourlyBurn": "21",
    "creditsPerActiveMonitorDay": "500",
    "creditsPerActiveMonitorHour": "21",
    "eventsIncluded": true,
    "instantCheckIntervalSeconds": 1,
    "unlimitedSlots": true
  },
  "creditInfo": {
    "balance": "77000",
    "lifetimePurchased": "140000",
    "lifetimeUsed": "63000",
    "autoTopupEnabled": false,
    "autoTopupAmountDollars": 10,
    "autoTopupThreshold": "50000"
  },
  "xUsername": "elonmusk"
}
```

### Update Account

```
PATCH /account
```

Update account locale. Session auth only (not API key).

**Body:** `{ "locale": "en" | "tr" | "es" }`

---

## API Keys

Session auth only. These endpoints do not accept API key auth.

### Create API Key

```
POST /api-keys
```

**Body:** `{ "name": "My Key" }` (optional)

**Response:** Returns `fullKey` (shown only once), `prefix`, `name`, `id`, `createdAt`.

### List API Keys

```
GET /api-keys
```

Returns all keys with `id`, `name`, `prefix`, `isActive`, `createdAt`, `lastUsedAt`. Full key is never exposed.

### Revoke API Key

```
DELETE /api-keys/{id}
```

Permanent and irreversible. The key stops working immediately.

---

## Monitors

### Create Monitor

```
POST /monitors
```

**Body:**
```json
{
  "username": "elonmusk",
  "eventTypes": ["tweet.new", "tweet.reply", "tweet.quote"]
}
```

**Response:**
```json
{
  "id": "7",
  "username": "elonmusk",
  "xUserId": "44196397",
  "eventTypes": ["tweet.new", "tweet.reply", "tweet.quote"],
  "createdAt": "2026-02-24T10:30:00.000Z"
}
```

Event types: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet`, `webhook.test`.

Returns `409 monitor_already_exists` if the username is already monitored.

### List Monitors

```
GET /monitors
```

Returns all monitors (up to 200, no pagination). Response includes `monitors` array and `total` count.

### Get Monitor

```
GET /monitors/{id}
```

### Update Monitor

```
PATCH /monitors/{id}
```

**Body:** `{ "eventTypes": [...], "isActive": true|false }` (both optional)

### Delete Monitor

```
DELETE /monitors/{id}
```

Stops tracking and deletes all associated data.

---

## Events

### List Events

```
GET /events
```

**Query parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `monitorId` | string | Filter by monitor ID |
| `eventType` | string | Filter by event type |
| `limit` | number | Results per page (1-100, default 50) |
| `after` | string | Cursor for next page |

**Response:**
```json
{
  "events": [
    {
      "id": "9010",
      "type": "tweet.new",
      "monitorId": "7",
      "username": "elonmusk",
      "occurredAt": "2026-02-24T16:45:00.000Z",
      "data": {
        "tweetId": "1893556789012345678",
        "text": "Hello world",
        "metrics": { "likes": 3200, "retweets": 890, "replies": 245 }
      }
    }
  ],
  "hasMore": true,
  "nextCursor": "MjAyNi0wMi0yNFQxNjozMDowMC4wMDBa..."
}
```

### Get Event

```
GET /events/{id}
```

Returns a single event with full details.

---

## Webhooks

### Create Webhook

```
POST /webhooks
```

**Body:**
```json
{
  "url": "https://your-server.com/webhook",
  "eventTypes": ["tweet.new", "tweet.reply"]
}
```

**Response** includes a `secret` field (shown only once). Store it for signature verification.

### List Webhooks

```
GET /webhooks
```

Returns all webhooks (up to 200). Secret is never exposed in list responses.

### Update Webhook

```
PATCH /webhooks/{id}
```

**Body:** `{ "url": "...", "eventTypes": [...], "isActive": true|false }` (all optional)

### Delete Webhook

```
DELETE /webhooks/{id}
```

Permanently removes the webhook. All future deliveries are stopped.

### Test Webhook

```
POST /webhooks/{id}/test
```

Sends a `webhook.test` event to the webhook endpoint, HMAC-signed with the webhook's secret. Returns success or failure status with HTTP response details.

**Payload delivered to your endpoint:**
```json
{
  "eventType": "webhook.test",
  "data": {
    "message": "Test delivery from Xquik"
  },
  "timestamp": "2026-02-27T12:00:00.000Z"
}
```

The delivery includes the `X-Xquik-Signature` header, identical to production deliveries.

Returns `400 webhook_inactive` if the webhook is disabled. Reactivate via `PATCH /webhooks/{id}` before testing.

### List Deliveries

```
GET /webhooks/{id}/deliveries
```

View delivery attempts and statuses for a webhook. Statuses: `pending`, `delivered`, `failed`, `exhausted`.

---

## Draws

### Create Draw

```
POST /draws
```

Run a giveaway draw from a tweet. Picks random winners from replies.

**Body:**
```json
{
  "tweetUrl": "https://x.com/user/status/1893456789012345678",
  "winnerCount": 3,
  "backupCount": 2,
  "uniqueAuthorsOnly": true,
  "mustRetweet": true,
  "mustFollowUsername": "burakbayir",
  "filterMinFollowers": 100,
  "filterAccountAgeDays": 30,
  "filterLanguage": "en",
  "requiredKeywords": ["giveaway"],
  "requiredHashtags": ["#contest"],
  "requiredMentions": ["@xquik"]
}
```

All filter fields are optional. Only `tweetUrl` is required.

**Response:**
```json
{
  "id": "42",
  "tweetId": "1893456789012345678",
  "tweetUrl": "https://x.com/user/status/1893456789012345678",
  "tweetText": "Like & RT to enter! Picking 3 winners tomorrow.",
  "tweetAuthorUsername": "xquik",
  "tweetLikeCount": 4200,
  "tweetRetweetCount": 1800,
  "tweetReplyCount": 1500,
  "tweetQuoteCount": 120,
  "status": "completed",
  "totalEntries": 1500,
  "validEntries": 890,
  "createdAt": "2026-02-24T10:00:00.000Z",
  "drawnAt": "2026-02-24T10:01:00.000Z"
}
```

### List Draws

```
GET /draws
```

Cursor-paginated. Returns compact draw objects.

### Get Draw

```
GET /draws/{id}
```

Returns full draw details including winners.

### Export Draw

```
GET /draws/{id}/export?format=csv&type=winners
```

Formats: `csv`, `json`, `md`, `md-document`, `pdf`, `txt`, `xlsx`. Types: `winners` (default), `entries`. Entry exports capped at 100,000 rows (PDF capped at 10,000).

---

## Extractions

### Create Extraction

```
POST /extractions
```

Run a bulk data extraction job. See `references/extractions.md` for all 23 tool types.

**Body:**
```json
{
  "toolType": "reply_extractor",
  "targetTweetId": "1893704267862470862",
  "resultsLimit": 500
}
```

`resultsLimit` (optional): Maximum results to extract. Stops early instead of fetching all data. Useful for controlling costs.

**Tweet Search Filters** (`tweet_search_extractor` only):

| Field | Type | Description |
|-------|------|-------------|
| `fromUser` | string | Author username |
| `toUser` | string | Directed to user |
| `mentioning` | string | Mentions user |
| `language` | string | Language code (e.g., `en`) |
| `sinceDate` | string | Start date (YYYY-MM-DD) |
| `untilDate` | string | End date (YYYY-MM-DD) |
| `mediaType` | string | `images`, `videos`, `gifs`, or `media` |
| `minFaves` | number | Minimum likes |
| `minRetweets` | number | Minimum retweets |
| `minReplies` | number | Minimum replies |
| `verifiedOnly` | boolean | Verified authors only |
| `replies` | string | `include`, `exclude`, or `only` |
| `retweets` | string | `include`, `exclude`, or `only` |
| `exactPhrase` | string | Exact match text |
| `excludeWords` | string | Comma-separated words to exclude |
| `advancedQuery` | string | Raw X search operators appended to query |

These filters are converted to X search operators and combined with `searchQuery`.

**Response:**
```json
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "toolType": "reply_extractor",
  "status": "running"
}
```

### Estimate Extraction

```
POST /extractions/estimate
```

Preview the cost before running. Same body as create.

**Response:**
```json
{
  "allowed": true,
  "creditsAvailable": "50000",
  "creditsRequired": "150",
  "source": "replyCount",
  "estimatedResults": 150
}
```

### List Extractions

```
GET /extractions
```

Cursor-paginated. Filter by `status` and `toolType`.

### Get Extraction

```
GET /extractions/{id}
```

Returns job details with paginated results (up to 1,000 per page).

### Export Extraction

```
GET /extractions/{id}/export?format=csv
```

Formats: `csv`, `json`, `md`, `md-document`, `pdf`, `txt`, `xlsx`. 100,000 row limit (PDF 10,000). Exports include enrichment columns not in the API response.

---

## X API (Direct Lookups)

Metered operations that deduct credits from the account balance.

### Get Tweet

```
GET /x/tweets/{id}
```

Returns full tweet with engagement metrics (likes, retweets, replies, quotes, views, bookmarks), author info (username, followers, verified status, profile picture), and optional attached media (photos/videos with URLs).

### Get Article

```
GET /x/articles/{id}
```

Retrieve the full content of an X Article (long-form post) by tweet ID. Returns title, body text with block-level formatting, cover image, inline images, and engagement metrics. Metered.

**Response:**
```json
{
  "title": "Why AI Will Transform Everything",
  "coverImage": "https://pbs.twimg.com/...",
  "bodyHtml": "<p>The future of AI...</p>",
  "likeCount": 5200,
  "retweetCount": 890,
  "replyCount": 245,
  "viewCount": 150000,
  "bookmarkCount": 1200,
  "author": {
    "id": "44196397",
    "username": "elonmusk",
    "name": "Elon Musk"
  }
}
```

### Search Tweets

```
GET /x/tweets/search?q={query}
```

Search using X syntax: keywords, `#hashtags`, `from:user`, `to:user`, `"exact phrases"`, `OR`, `-exclude`.

Returns tweet info with optional engagement metrics (likeCount, retweetCount, replyCount) and optional attached media. Some fields may be omitted if unavailable.

### Get User

```
GET /x/users/{username}
```

Returns profile info. Fields `id`, `username`, `name` are always present. All other fields (`description`, `followers`, `following`, `verified`, `profilePicture`, `location`, `createdAt`, `statusesCount`) are optional and omitted when unavailable.

### Check Follower

```
GET /x/followers/check?source={username}&target={username}
```

Returns `isFollowing` and `isFollowedBy` for both directions.

### Get User Tweets

```
GET /x/users/{id}/tweets
```

Get a user's recent tweets by user ID. Metered (1 credit/tweet).

### Get User Likes

```
GET /x/users/{id}/likes
```

Get tweets liked by a user. Metered (1 credit/result).

### Get User Media

```
GET /x/users/{id}/media
```

Get a user's media tweets (tweets containing photos/videos). Metered (1 credit/result).

### Get Tweet Favoriters

```
GET /x/tweets/{id}/favoriters
```

Get users who liked a tweet. Metered (1 credit/result).

### Get Mutual Followers

```
GET /x/users/{id}/followers-you-know
```

Get mutual followers (followers you know). Metered (1 credit/result).

### Get Bookmarks

```
GET /x/bookmarks
```

Get bookmarked tweets. Requires a connected X account. Metered (1 credit/result).

**Sensitive:** Returns private data. Confirm with user before calling.

### Get Bookmark Folders

```
GET /x/bookmarks/folders
```

Get bookmark folders. Requires a connected X account. Metered (1 credit).

### Get Notifications

```
GET /x/notifications
```

Get notifications with type filter. Requires a connected X account. Metered (1 credit/result).

**Sensitive:** Returns private data. Confirm with user before calling.

### Get Home Timeline

```
GET /x/timeline
```

Get home timeline. Requires a connected X account. Metered (1 credit/result).

**Sensitive:** Returns private data. Confirm with user before calling.

---

## X Media (Download)

### Download Media

```
POST /x/media/download
```

Download images, videos, and GIFs from tweets. Single or bulk (up to 50). Returns a shareable gallery URL.

**Body:** Provide either `tweetInput` (single tweet) or `tweetIds` (bulk). Exactly 1 is required.

| Field | Type | Description |
|-------|------|-------------|
| `tweetInput` | string | Tweet URL or numeric tweet ID for a single download. Accepts `x.com` and `twitter.com` URL formats |
| `tweetIds` | string[] | Array of tweet URLs or IDs for bulk download. Maximum 50 items. Returns a single combined gallery |

**Response (single):**
```json
{
  "tweetId": "1893456789012345678",
  "galleryUrl": "https://xquik.com/gallery/abc123",
  "cacheHit": false
}
```

**Response (bulk):**
```json
{
  "galleryUrl": "https://xquik.com/gallery/def456",
  "totalTweets": 3,
  "totalMedia": 7
}
```

First download is metered. Subsequent requests for the same tweet return cached URLs at no cost (`cacheHit: true`). All downloads are saved to the gallery at `https://xquik.com/gallery`.

Returns `400 no_media` if the tweet has no downloadable media. Returns `400 too_many_tweets` if bulk array exceeds 50 items.

---

## Trends

### List Trends

```
GET /trends?woeid=1&count=30
```

Metered. Subscription required. Cached, refreshes every 15 minutes.

**WOEIDs:** 1 (Worldwide), 23424977 (US), 23424975 (UK), 23424969 (Turkey), 23424950 (Spain), 23424829 (Germany), 23424819 (France), 23424856 (Japan), 23424848 (India), 23424768 (Brazil), 23424775 (Canada), 23424900 (Mexico).

**Response:**
```json
{
  "trends": [
    { "name": "#AI", "description": "...", "rank": 1, "query": "#AI" }
  ],
  "total": 30,
  "woeid": 1
}
```

---

## Radar

### List Radar Items

```
GET /radar
```

Get trending topics and news from 7 sources: Google Trends, Hacker News, Polymarket, TrustMRR, Wikipedia, GitHub Trending, Reddit. Free.

**Query parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `source` | string | Filter by source: `google_trends`, `hacker_news`, `polymarket`, `trustmrr`, `wikipedia`, `github`, `reddit` |
| `category` | string | Filter by category: `general`, `tech`, `dev`, `science`, `culture`, `politics`, `business`, `entertainment` |
| `limit` | number | Items per page (1-100, default 50) |
| `hours` | number | Look-back window in hours (1-72, default 6) |
| `region` | string | Region code: `US`, `GB`, `TR`, `ES`, `DE`, `FR`, `JP`, `IN`, `BR`, `CA`, `MX`, `global` (default) |

**Response:**
```json
{
  "items": [
    {
      "id": "12345",
      "title": "Claude 4.6 Released",
      "description": "Anthropic releases Claude 4.6...",
      "url": "https://example.com/article",
      "imageUrl": "https://example.com/image.png",
      "source": "hacker_news",
      "sourceId": "hn_12345",
      "category": "tech",
      "region": "global",
      "language": "en",
      "score": 450,
      "metadata": { "points": 450, "numberComments": 132, "author": "pgdev" },
      "publishedAt": "2026-03-05T10:00:00.000Z",
      "createdAt": "2026-03-05T10:05:00.000Z"
    }
  ],
  "hasMore": true,
  "nextCursor": "NDUwfDIwMjYtMDMtMDRUMDg6MzA6MDAuMDAwWnwxMjM0NQ=="
}
```

Fields: `id`, `title`, `description?`, `url?`, `imageUrl?`, `source`, `sourceId`, `category`, `region`, `language`, `score`, `metadata`, `publishedAt`, `createdAt`. Response includes `hasMore` and `nextCursor` for pagination.

---

## Compose

### Compose Tweet

```
POST /compose
```

Compose, refine, and score tweets using X algorithm data. Free, 3-step workflow.

**Body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `step` | string | Yes | `compose`, `refine`, or `score` |
| `topic` | string | No | Tweet topic (compose, refine) |
| `goal` | string | No | `engagement`, `followers`, `authority`, `conversation` |
| `styleUsername` | string | No | Cached style username for voice matching (compose) |
| `tone` | string | No | Desired tone (refine) |
| `additionalContext` | string | No | Extra context or URLs (refine) |
| `callToAction` | string | No | Desired CTA (refine) |
| `mediaType` | string | No | `photo`, `video`, `none` (refine) |
| `draft` | string | No | Tweet text to evaluate (score) |
| `hasLink` | boolean | No | Link attached (score) |
| `hasMedia` | boolean | No | Media attached (score) |

**Response (step=compose):** Returns `contentRules`, `scorerWeights`, `followUpQuestions`, `algorithmInsights`, `engagementMultipliers`, `topPenalties`.

**Response (step=refine):** Returns `compositionGuidance`, `examplePatterns`.

**Response (step=score):** Returns `totalChecks`, `passedCount`, `topSuggestion`, `checklist[]` with `factor`, `passed`, `suggestion`.

---

## Drafts

### Create Draft

`POST /drafts`

Save a tweet draft for later.

**Request body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `text` | string | Yes | The draft tweet text |
| `topic` | string | No | Topic the tweet is about |
| `goal` | string | No | Optimization goal: `engagement`, `followers`, `authority`, `conversation` |

**Response (201):**

```json
{
  "id": "123",
  "text": "draft text",
  "topic": "product launch",
  "goal": "engagement",
  "createdAt": "2026-02-24T10:30:00.000Z",
  "updatedAt": "2026-02-24T10:30:00.000Z"
}
```

---

### List Drafts

`GET /drafts`

List saved tweet drafts with cursor pagination.

**Query parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `limit` | number | No | 50 | Results per page (max 50) |
| `afterCursor` | string | No | - | Pagination cursor from previous response |

**Response (200):**

```json
{
  "drafts": [
    {
      "id": "123",
      "text": "draft text",
      "topic": "product launch",
      "goal": "engagement",
      "createdAt": "2026-02-24T10:30:00.000Z",
      "updatedAt": "2026-02-24T10:30:00.000Z"
    }
  ],
  "afterCursor": "cursor_string",
  "hasMore": true
}
```

---

### Get Draft

`GET /drafts/{id}`

Get a specific draft by ID.

**Response (200):** Single draft object.

**Errors:** `400 invalid_id`, `404 draft_not_found`

---

### Delete Draft

`DELETE /drafts/{id}`

Delete a draft. Returns `204 No Content`.

**Errors:** `400 invalid_id`, `404 draft_not_found`

---

## Tweet Style Cache

### Analyze & Cache Style

`POST /styles`

Fetch recent tweets from an X account and cache them for style analysis. **Consumes API usage credits.**

**Request body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `username` | string | Yes | X username to analyze (without @) |

**Response (201):**

```json
{
  "xUsername": "elonmusk",
  "tweetCount": 20,
  "isOwnAccount": false,
  "fetchedAt": "2026-02-24T10:30:00.000Z",
  "tweets": [
    {
      "id": "1893456789012345678",
      "text": "The future is now.",
      "authorUsername": "elonmusk",
      "createdAt": "2026-02-24T14:22:00.000Z"
    }
  ]
}
```

---

### List Cached Styles

`GET /styles`

List all cached tweet style profiles. Max 200 results, ordered by fetch date.

**Response (200):**

```json
{
  "styles": [
    {
      "xUsername": "elonmusk",
      "tweetCount": 20,
      "isOwnAccount": false,
      "fetchedAt": "2026-02-24T10:30:00.000Z"
    }
  ]
}
```

---

### Save Custom Style

`PUT /styles/{username}`

Save a custom style profile from tweet texts. Free, no usage cost. Replaces existing style if one exists with the same label.

**Body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `label` | string | Yes | Style label name (1-30 characters) |
| `tweets` | object[] | Yes | Array of tweet objects (1-100). Each must have a `text` field |

**Response (200):** Style object with label, `tweetCount`, `isOwnAccount: false`, `fetchedAt`, and `tweets` array.

**Errors:** `400 invalid_input`

---

### Get Cached Style

`GET /styles/{username}`

Get a cached style profile with full tweet data.

**Response (200):** Full style object with `tweets` array.

**Errors:** `404 style_not_found`

---

### Delete Cached Style

`DELETE /styles/{username}`

Delete a cached style. Returns `204 No Content`.

**Errors:** `404 style_not_found`

---

### Compare Styles

`GET /styles/compare?username1=A&username2=B`

Compare two cached tweet style profiles side by side.

**Query parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `username1` | string | Yes | First X username |
| `username2` | string | Yes | Second X username |

**Response (200):**

```json
{
  "style1": { "xUsername": "user1", "tweetCount": 20, "isOwnAccount": true, "fetchedAt": "...", "tweets": [...] },
  "style2": { "xUsername": "user2", "tweetCount": 15, "isOwnAccount": false, "fetchedAt": "...", "tweets": [...] }
}
```

**Errors:** `400 missing_params`, `404 style_not_found`

---

### Analyze Performance

`GET /styles/{username}/performance`

Get live engagement metrics for cached tweets. **Consumes API usage credits.**

**Response (200):**

```json
{
  "xUsername": "elonmusk",
  "tweetCount": 20,
  "tweets": [
    {
      "id": "1893456789012345678",
      "text": "The future is now.",
      "likeCount": 42000,
      "retweetCount": 8500,
      "replyCount": 3200,
      "quoteCount": 1100,
      "viewCount": 5000000,
      "bookmarkCount": 2400
    }
  ]
}
```

**Errors:** `404 style_not_found`

---

## Account Identity

### Set X Identity

`PUT /account/x-identity`

Link your X username to your Xquik account. Required for own-account detection in style analysis.

**Request body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `username` | string | Yes | Your X username (without @) |

**Response (200):**

```json
{
  "success": true,
  "xUsername": "elonmusk"
}
```

**Errors:** `400 invalid_input`

---

## Subscribe

### Get Subscription Link

```
POST /subscribe
```

Returns a checkout URL for subscribing or managing the subscription. If already subscribed, returns the billing portal URL. Use only after the user confirms the plan or billing action.

**Response:**
```json
{
  "url": "https://checkout.xquik.com/..."
}
```

---

## X Accounts (Connected)

Manage connected X accounts for confirmation-gated write actions. All endpoints are free (no usage cost).

**Connecting or re-authenticating an X account is done by the user in the Xquik dashboard** at [xquik.com/dashboard/account](https://xquik.com/dashboard/account), not via this skill. The skill never handles X login material. The agent should direct the user to the dashboard when a new account needs to be connected or an existing session needs to be refreshed.

### List X Accounts

```
GET /x/accounts
```

Returns all connected X accounts. Response: `{ accounts: [{ id, username, displayName, isActive, createdAt }] }`.

### Get X Account

```
GET /x/accounts/{id}
```

Returns `{ id, username, displayName, isActive, createdAt }`.

### Disconnect X Account

```
DELETE /x/accounts/{id}
```

Permanently removes the account from Xquik. Returns `{ success: true }`. Before calling, confirm with the user.

---

## X Write

Write actions performed through connected X accounts. All endpoints are metered. Every request requires an `account` field (username or account ID) identifying which connected account to use.

### Create Tweet

```
POST /x/tweets
```

**Body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `account` | string | Yes | Connected X username or account ID |
| `text` | string | Yes | Tweet text (280 chars, or 25,000 if `is_note_tweet` is true) |
| `reply_to_tweet_id` | string | No | Tweet ID to reply to |
| `attachment_url` | string | No | URL to attach as a card |
| `community_id` | string | No | Community ID to post into |
| `is_note_tweet` | boolean | No | Long-form note tweet (up to 25,000 chars) |
| `media_ids` | string[] | No | Media IDs from `POST /x/media` (max 4 images or 1 video) |

**Response:** `{ tweetId, success: true }`

**Errors:** `502 x_write_failed`

### Delete Tweet

```
DELETE /x/tweets/{id}
```

**Body:** `{ "account": "username" }`

**Response:** `{ success: true }`

### Like Tweet

```
POST /x/tweets/{id}/like
```

**Body:** `{ "account": "username" }`

### Unlike Tweet

```
DELETE /x/tweets/{id}/like
```

**Body:** `{ "account": "username" }`

### Retweet

```
POST /x/tweets/{id}/retweet
```

**Body:** `{ "account": "username" }`

### Follow User

```
POST /x/users/{id}/follow
```

**Body:** `{ "account": "username" }`

**Errors:** `502 x_write_failed`

### Unfollow User

```
DELETE /x/users/{id}/follow
```

**Body:** `{ "account": "username" }`

### Remove Follower

```
POST /x/users/{id}/remove-follower
```

Remove a user from your followers without blocking them.

**Body:** `{ "account": "username" }`

**Cost:** 10 credits per call.

### Send DM

```
POST /x/dm/{userId}
```

**Body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `account` | string | Yes | Connected X username or account ID |
| `text` | string | Yes | Message text |
| `media_ids` | string[] | No | Media IDs to attach |
| `reply_to_message_id` | string | No | Message ID to reply to |

### Get DM History

```
GET /x/dm/{userId}/history
```

Get DM conversation history with a user. Requires a connected X account. Metered (1 credit/result).

**Sensitive:** Returns private DM conversations. Confirm with user before calling. Do not forward to other tools without consent.

### Update Profile

```
PATCH /x/profile
```

**Body:** `{ "account": "username", "name": "...", "description": "...", "location": "...", "url": "..." }` (account required, others optional)

### Update Avatar

```
PATCH /x/profile/avatar
```

Update profile avatar. Max 700 KB, GIF/JPEG/PNG. Metered (10 credits).

**Body:** FormData with `account` (required) and `file` (required, max 700 KB).

### Update Banner

```
PATCH /x/profile/banner
```

Update profile banner. Max 2 MB, GIF/JPEG/PNG. Metered (10 credits).

**Body:** FormData with `account` (required) and `file` (required, max 2 MB).

### Upload Media

```
POST /x/media
```

**Body:** FormData with `account` (required), `file` (required), and `is_long_video` (optional boolean). Alternatively, JSON body with `account` (required) and `url` (required, direct media URL) for URL-based upload.

**Response:** Returns a media ID to pass in `media_ids` when creating a tweet.

### Create Community

```
POST /x/communities
```

**Body:** `{ "account": "username", "name": "...", "description": "..." }` (all required)

### Delete Community

```
DELETE /x/communities/{id}
```

**Body:** `{ "account": "username", "community_name": "..." }` (name required for confirmation)

### Join Community

```
POST /x/communities/{id}/join
```

**Body:** `{ "account": "username" }`

**Errors:** `409 already_member`

### Leave Community

```
DELETE /x/communities/{id}/join
```

**Body:** `{ "account": "username" }`

---

## Credits

### Get Credit Balance

```
GET /credits
```

Get credit balance, lifetime purchased/used, and auto top-up status. Free.

### Top Up Credits

```
POST /credits/topup
```

Get a checkout URL to purchase credits ($10 minimum). Use only after the user confirms the exact amount.

### Get Top-Up Status

```
GET /credits/topup/status
```

Poll a checkout session after starting a credit top-up. Query: `session_id`. Free.

### Quick Top Up

```
POST /credits/quick-topup
```

Charge a saved payment method for credits after the user confirms the exact amount. Body: `{ "dollars": 10 }` ($10 minimum, $500 maximum). Free endpoint, but it can initiate a confirmed card charge.

Responses include `{ "outcome": "charged", "credits": "...", "balance": "..." }`, `{ "outcome": "no_payment_method" }`, or `{ "outcome": "requires_action", "clientSecret": "..." }`.

---

## Support

### Create Ticket

```
POST /support/tickets
```

**Body:** `{ "subject": "...", "body": "..." }`

**Response (201):** `{ id, subject, status, createdAt }`

### List Tickets

```
GET /support/tickets
```

Returns all tickets for the authenticated user.

### Get Ticket

```
GET /support/tickets/{id}
```

Returns ticket with messages.

### Update Ticket

```
PATCH /support/tickets/{id}
```

Update ticket status.

### Reply to Ticket

```
POST /support/tickets/{id}/messages
```

**Body:** `{ "body": "..." }`

Add a message to an existing ticket.

---

## Error Codes

| Status | Code | Meaning |
|--------|------|---------|
| 400 | `invalid_input` | Request body failed validation |
| 400 | `invalid_id` | Path parameter is not a valid ID |
| 400 | `invalid_json` | Invalid JSON in request body |
| 400 | `invalid_tweet_url` | Tweet URL format is invalid |
| 400 | `invalid_tweet_id` | Tweet ID is empty or invalid |
| 400 | `invalid_username` | X username is empty or invalid |
| 400 | `invalid_tool_type` | Extraction tool type not recognized |
| 400 | `invalid_format` | Export format not `csv`, `json`, `md`, `md-document`, `pdf`, `txt`, or `xlsx` |
| 400 | `invalid_params` | Export query parameters are missing or invalid |
| 400 | `missing_query` | Required query parameter is missing |
| 400 | `missing_params` | Required query parameters are missing |
| 400 | `no_media` | Tweet has no downloadable media |
| 400 | `webhook_inactive` | Webhook is disabled (test-webhook only) |
| 401 | `unauthenticated` | Missing or invalid API key |
| 403 | `account_needs_reauth` | X account session expired; use dashboard re-auth flow |
| 402 | `no_subscription` | No active subscription |
| 402 | `subscription_inactive` | Subscription is not active |
| 402 | `no_credits` | No credit balance record exists |
| 402 | `insufficient_credits` | Credit balance is too low |
| 403 | `api_key_limit_reached` | API key limit reached (100 max) |
| 404 | `not_found` | Resource does not exist |
| 404 | `user_not_found` | X user not found |
| 404 | `tweet_not_found` | Tweet not found |
| 404 | `style_not_found` | No cached style found |
| 404 | `draft_not_found` | Draft not found |
| 409 | `monitor_already_exists` | Duplicate monitor for same username |
| 422 | `login_failed` | Account connection failed; use dashboard re-auth flow |
| 429 | - | Rate limited. Retry with backoff |
| 429 | `x_api_rate_limited` | X data source rate limited. Retry |
| 500 | `internal_error` | Server error |
| 502 | `stream_registration_failed` | Stream registration failed. Retry |
| 502 | `x_api_unavailable` | X data source temporarily unavailable |
| 502 | `x_api_unauthorized` | X data source authentication failed. Retry |
