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

# Create a tweet draft for review with the Xquik API

> Save one Xquik tweet draft with text, optional topic and goal, 25,000-character validation, API authentication, and clear 400, 401 or 429 recovery steps.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="201" id="response-drafts-create-201">
      ```json theme={null}
      {
        "id": "42",
        "text": "AI is the future of productivity",
        "topic": "AI trends",
        "goal": "engagement",
        "createdAt": "2025-01-15T12:00:00Z",
        "updatedAt": "2025-01-16T09:30:00Z"
      }
      ```
    </Tab>

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

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

    <Tab title="429" id="response-drafts-create-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>

## Save a tweet draft before publishing

Create one private tweet draft inside your authenticated Xquik account. Supply
the proposed text and optional composition context. The response returns a
stable Xquik draft ID for review or deletion.

This request does not publish a tweet. It sends nothing to followers and
creates no replies, reposts, likes, impressions, or public tweet ID.

Xquik drafts are separate from [X's native Unsent posts](https://help.x.com/en/using-x/how-to-post).
Creating a record here does not add anything to the X compose interface.

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

<CodeGroup>
  ```bash cURL theme={null}
  curl --fail-with-body \
    --request POST https://xquik.com/api/v1/drafts \
    --header "x-api-key: xq_YOUR_KEY_HERE" \
    --header "Content-Type: application/json" \
    --data '{
      "text": "Just shipped dark mode. What feature should we build next?",
      "topic": "product update",
      "goal": "conversation"
    }' | jq
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://xquik.com/api/v1/drafts", {
    method: "POST",
    headers: {
      "x-api-key": "xq_YOUR_KEY_HERE",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      text: "Just shipped dark mode. What feature should we build next?",
      topic: "product update",
      goal: "conversation",
    }),
  });
  const draft = await response.json();
  if (!response.ok) {
    throw new Error(`${response.status} ${draft.error}: ${draft.message}`);
  }

  console.log(draft.id, draft.text, draft.createdAt);
  ```

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

  response = requests.post(
      "https://xquik.com/api/v1/drafts",
      headers={"x-api-key": "xq_YOUR_KEY_HERE"},
      json={
          "text": "Just shipped dark mode. What feature should we build next?",
          "topic": "product update",
          "goal": "conversation",
      },
  )
  draft = response.json()
  if response.status_code != 201:
      raise RuntimeError(
          f'{response.status_code} {draft["error"]}: {draft["message"]}'
      )

  print(draft["id"], draft["text"], draft["createdAt"])
  ```

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

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

  func main() {
      body, err := json.Marshal(map[string]interface{}{
          "text":  "Just shipped dark mode. What feature should we build next?",
          "topic": "product update",
          "goal":  "conversation",
      })
      if err != nil {
          panic(err)
      }

      req, err := http.NewRequest("POST", "https://xquik.com/api/v1/drafts", bytes.NewReader(body))
      if err != nil {
          panic(err)
      }
      req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE")
      req.Header.Set("Content-Type", "application/json")

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

      var draft map[string]interface{}
      if err := json.NewDecoder(resp.Body).Decode(&draft); err != nil {
          panic(err)
      }
      if resp.StatusCode != http.StatusCreated {
          panic(fmt.Sprintf("%d %v: %v", resp.StatusCode, draft["error"], draft["message"]))
      }

      fmt.Println(draft["id"], draft["text"], draft["createdAt"])
  }
  ```
</CodeGroup>

## Choose the tweet draft fields

Send a JSON object with 1 required field and 2 optional fields.

| Field | Requirement | Draft use |
| - | - | - |
| `text` | Required string with 1 to 25,000 characters | Store the exact proposed tweet text. |
| `topic` | Optional string, stored up to 500 characters | Record the intended subject. |
| `goal` | Optional supported enum value | Record the intended composition outcome. |

The supported `goal` values are `engagement`, `followers`, `authority`, and
`conversation`. Match the lowercase value exactly.

The API silently omits an unsupported `goal`. It also omits a non-string goal.
Validate this field before sending the request.

The API silently truncates a topic longer than 500 characters. It omits a
non-string topic. Trim and validate the topic inside your client.

The `text` field behaves differently. Missing, empty, non-string, or oversized
text returns `400 invalid_input`. Xquik never silently truncates draft text.

The 25,000-character storage limit is not a publishing guarantee. Confirm the
connected account's X posting rules before a separate write request.

## Read the created draft

A successful request returns `201 Created` and one draft object.

| Field | Meaning | Next action |
| - | - | - |
| `id` | Stable Xquik draft ID | Store it for retrieval or deletion. |
| `text` | Exact accepted text | Compare it with the submitted copy. |
| `topic` | Accepted optional topic | Confirm any truncation was acceptable. |
| `goal` | Accepted optional goal | Confirm the requested value was supported. |
| `createdAt` | ISO 8601 creation timestamp | Record when review began. |
| `updatedAt` | ISO 8601 update timestamp | Compare later retrieval with this response. |

The API omits optional `topic` and `goal` fields when unset or invalid. Do not
expect those keys to contain `null`.

The response contains no thread order, media attachment, reply target,
schedule, publishing status, or public tweet ID.

## Build a tweet draft review workflow

Create a draft when a person or agent must approve text before publishing.

1. Prepare one exact tweet text string.
2. Add a short topic when reviewers need context.
3. Choose one supported composition goal.
4. Create the Xquik draft once.
5. Store the returned draft ID.
6. Retrieve that ID before final approval.
7. Compare text, context, and timestamps.
8. Publish approved text through a separate X write route.
9. Delete the saved draft after retention requirements allow it.

This route provides no edit operation. Create a replacement draft when text
must change. Keep the old ID until reviewers approve the replacement.

This route also provides no idempotency key. Repeating the same successful
request creates another draft record. Store every `201` response before creating
again. After a lost response, list and reconcile drafts before retrying.

Use [List Drafts](/api-reference/drafts/list) to reconcile duplicate records.
Use [Get Draft](/api-reference/drafts/get) before publishing or deleting one.

## Keep Xquik and native X drafts separate

Native X drafts appear under Unsent posts in the X compose interface. Native
thread drafts can contain several connected posts. Native drafts can also
carry photos, GIFs, or video.

An Xquik draft stores one text string plus optional composition context. It
does not mirror native draft features or publish automatically.

| Draft capability | Xquik create draft | Native X draft |
| - | - | - |
| Store one text string through an API | Yes | No through this endpoint |
| Store optional topic and goal | Yes | Not represented here |
| Appear under X Unsent posts | No | Yes |
| Keep a native thread sequence | No | Supported by native X workflows |
| Keep native media attachments | No | Supported by native X workflows |
| Publish during draft creation | No | No |

Keep thread items, media IDs, reply targets, and scheduling instructions in
your publishing workflow. Do not assume the draft response contains them.

## Recover from draft creation errors

| Status | Error | Cause | Fix |
| - | - | - | - |
| `201` | Draft object | Xquik stored the draft | Save the returned ID. |
| `400` | `invalid_input` | `text` is missing, empty, non-string, or oversized | Send 1 to 25,000 characters. |
| `401` | `unauthenticated` | The credential is missing or invalid | Replace the API key or bearer token. |
| `429` | `rate_limit_exceeded` | Too many requests reached the route | Wait for `Retry-After`, then retry once. |

Never retry `400` without changing the body. Never retry `401` without
replacing the credential.

After `429`, keep the submitted text. Retry only after the server's delay.
Reconcile the list when the original request may have succeeded.

## Tweet draft creation questions

### How can I save a tweet draft through an API?

Send `POST /drafts` with a JSON `text` string. Authenticate with an Xquik API
key or OAuth bearer token.

### Does creating a draft publish the tweet?

No. The request stores private Xquik text only. Publish through a separate
write route after approval.

### Does the draft appear in X unsent posts?

No. Xquik draft records and native X drafts are separate collections.

### Can one draft store a Twitter thread?

No. One draft stores one text string. Create and keep thread structure in
your publishing workflow.

### Can I attach media or a reply target?

No. Draft fields contain no media or reply target. Add those
details during a separate publishing request.

### Can I update a saved tweet draft?

No update route exists. Create a replacement, approve it, then delete the old
record when safe.

### Does creating a tweet draft consume credits?

No. This authenticated draft creation request is free.

## Headers

<ParamField header="x-api-key" type="string">
  Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard).
</ParamField>

<ParamField header="Authorization" type="string">
  An OAuth bearer token formatted as `Bearer YOUR_TOKEN`. Send this header or `x-api-key`, not both.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`.
</ParamField>

## Body

<ParamField body="text" type="string" required>
  Exact draft text. Send 1 to 25,000 characters. Oversized text returns `400`.
</ParamField>

<ParamField body="topic" type="string">
  Optional composition topic. The API silently truncates values longer than 500 characters.
</ParamField>

<ParamField body="goal" type="string">
  Optional goal: `engagement`, `followers`, `authority`, or `conversation`. The API silently omits other values.
</ParamField>

## Response

### 201 Created

<ResponseField name="id" type="string">
  Unique Xquik draft ID.
</ResponseField>

<ResponseField name="text" type="string">
  Exact accepted tweet text.
</ResponseField>

<ResponseField name="topic" type="string">
  Optional accepted topic. Omitted when unset.
</ResponseField>

<ResponseField name="goal" type="string">
  Optional accepted goal. Omitted when unset.
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO 8601 creation timestamp.
</ResponseField>

<ResponseField name="updatedAt" type="string">
  ISO 8601 update timestamp.
</ResponseField>

```json theme={null}
{
  "id": "42",
  "text": "Just shipped dark mode. What feature should we build next?",
  "topic": "product update",
  "goal": "conversation",
  "createdAt": "2026-02-24T10:30:00.000Z",
  "updatedAt": "2026-02-24T10:30:00.000Z"
}
```

### 400 Invalid input

```json theme={null}
{ "error": "invalid_input", "message": "Invalid input. Check the request body." }
```

The `text` field is missing, empty, non-string, or longer than 25,000 characters.

### 401 Unauthenticated

```json theme={null}
{
  "error": "unauthenticated",
  "message": "Authentication required. Provide a valid API key or bearer token."
}
```

Authentication is missing or invalid. Replace the credential before retrying.

### 429 Rate limited

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

Too many requests reached the route. Wait for `Retry-After` before retrying.

<Note>
  **Next steps.** [List Drafts](/api-reference/drafts/list) to list saved text, [Get Draft](/api-reference/drafts/get) to retrieve one record, or [Delete Draft](/api-reference/drafts/delete) to remove it.
</Note>


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