> ## 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 API errors & X write action status

> Handle Twitter API errors by polling tweet, reply, DM, follow, like, repost, media, profile, and community writes. Check results, billing, and safe retries.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="200" id="response-x-write-get-write-action-status-200">
      ```json theme={null}
      {
        "object": "x_write_action",
        "id": "12345",
        "writeActionId": "12345",
        "action": "like",
        "status": "success",
        "terminal": true,
        "retryable": false,
        "safeToRetry": false,
        "statusUrl": "/api/v1/x/write-actions/12345",
        "pollAfterMs": null
      }
      ```
    </Tab>

    <Tab title="202" id="response-x-write-get-write-action-status-202">
      ```json theme={null}
      {
        "object": "x_write_action",
        "id": "12346",
        "writeActionId": "12346",
        "action": "create_tweet",
        "status": "dispatching",
        "terminal": false,
        "retryable": false,
        "safeToRetry": false,
        "statusUrl": "/api/v1/x/write-actions/12346",
        "pollAfterMs": 2000
      }
      ```
    </Tab>

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

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

    <Tab title="404" id="response-x-write-get-write-action-status-404">
      ```json theme={null}
      {
        "error": "not_found",
        "message": "Resource not found."
      }
      ```
    </Tab>

    <Tab title="429" id="response-x-write-get-write-action-status-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>

Every X write returns a stored `x_write_action` record.
Store its `id` or `writeActionId`.
Poll `statusUrl` whenever `terminal` is `false`.

## Handle Twitter API errors

Use this endpoint to handle errors after an Xquik write.
Xquik stores a write record in addition to the HTTP response code.
An error message does not prove that a write failed.
A timeout can occur after dispatch.
Trust `terminal`, `safeToRetry`, `sendDispatched`, and `nextAction`.
Review X's [response codes and errors](https://docs.x.com/x-api/fundamentals/response-codes-and-errors).
Xquik's write status record uses different fields.

<Note>
  Trust `terminal`, `safeToRetry`, and `nextAction`. Never infer retry safety
  from an HTTP status or error name alone.
</Note>

## Agent algorithm

1. Generate one unique `Idempotency-Key` for the intended write.
2. Submit the write once.
3. Store `id`, `request.hash`, `account`, `target`, `billing`, and `statusUrl`.
4. When `terminal` is `false`, wait for `Retry-After` or `pollAfterMs`.
5. Poll `statusUrl` until `terminal` is `true`.
6. Record `result` and settled `billing`.
7. Retry only when `safeToRetry` is `true`, using a new key.
8. When `nextAction.type` is `verify_result`, verify the external state first.

Reuse the original `Idempotency-Key` only to replay the same account,
action, target, and payload. The replay returns the first answer again.
It keeps the first status, body, and `Retry-After`, and adds `idempotent: true`.
While the first request runs, the replay returns `202` and the action.
A replay sends nothing to X and charges nothing.

## Lifecycle

| Status | Terminal | Required behavior |
| - | - | - |
| `accepted` | No | Poll. Dispatch has not completed. |
| `dispatching` | No | Poll. Do not submit another write. |
| `pending_confirmation` | No | Poll. The write may already exist. |
| `success` | Yes | Store the result and settled billing. |
| `failed` | Yes | Follow `safeToRetry` and `nextAction`. |
| `expired` | Yes | Verify the result when dispatch may have occurred. |

Writes X never confirms end `expired`. You pay nothing for them.
Their message can read "X did not confirm this action. Check your X account before you retry."
A post X never published fails as `x_post_not_sent`. You pay nothing, and `safeToRetry` is `true`.

Active actions return `202`, `Location`, and `Retry-After`. Terminal actions
return `200` from this endpoint.

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl https://xquik.com/api/v1/x/write-actions/42 \
    -H "x-api-key: xq_YOUR_KEY_HERE" | jq
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://xquik.com/api/v1/x/write-actions/42", {
    headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
  });
  const action = await response.json();

  if (!action.terminal) {
    const delayMs = action.pollAfterMs ?? 2000;
    process.stdout.write(JSON.stringify({ delayMs, next: action.statusUrl }) + "\n");
  } else if (action.safeToRetry) {
    process.stdout.write("Ask for approval, then retry with a new key.\n");
  } else {
    process.stdout.write(JSON.stringify(action.result) + "\n");
  }
  ```

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

  response = requests.get(
      "https://xquik.com/api/v1/x/write-actions/42",
      headers={"x-api-key": "xq_YOUR_KEY_HERE"},
  )
  action = response.json()

  if not action["terminal"]:
      print({"delay_ms": action["pollAfterMs"], "next": action["statusUrl"]})
  elif action["safeToRetry"]:
      print("Ask for approval, then retry with a new key.")
  else:
      print(action["result"])
  ```
</CodeGroup>

## Headers

<ParamField header="x-api-key" type="string">
  Send your Xquik API key in this header.
</ParamField>

<ParamField header="Authorization" type="string">
  Send an OAuth 2.1 bearer token instead of `x-api-key`.
</ParamField>

## Path parameters

<ParamField path="id" type="string" required>
  Action ID returned by the original write.
</ParamField>

## Response

<Tabs>
  <Tab title="200 Terminal">
    Polling is complete. Store `result` and settled `billing`. Follow
    `safeToRetry` and `nextAction` before any new attempt.
  </Tab>

  <Tab title="202 Active">
    Poll `statusUrl` after `Retry-After` or `pollAfterMs`. Never submit another
    write while `terminal` is `false`.
  </Tab>

  <Tab title="401 Unauthenticated">
    Fix the authentication credentials. Keep the original write record.
  </Tab>

  <Tab title="404 Not found">
    Check the action ID and environment. Never resubmit the original write.
  </Tab>

  <Tab title="429 Rate limited">
    Wait for `Retry-After`. Poll the same action again. Never resend the write.
  </Tab>
</Tabs>

<ResponseField name="object" type="string">
  Always `x_write_action`.
</ResponseField>

<ResponseField name="id" type="string">
  Durable action ID.
</ResponseField>

<ResponseField name="writeActionId" type="string">
  This field aliases `id`.
</ResponseField>

<ResponseField name="action" type="string">
  Exact write operation.
</ResponseField>

<ResponseField name="status" type="string">
  Current lifecycle status.
</ResponseField>

<ResponseField name="terminal" type="boolean">
  This field is `true` when polling can stop.
</ResponseField>

<ResponseField name="retryable" type="boolean">
  This field is `true` when a later attempt could succeed.
</ResponseField>

<ResponseField name="safeToRetry" type="boolean">
  This field is `true` when a new attempt is safe.
</ResponseField>

<ResponseField name="statusUrl" type="string">
  Relative polling URL.
</ResponseField>

<ResponseField name="pollAfterMs" type="number | null">
  Recommended polling delay.
</ResponseField>

<ResponseField name="charged" type="boolean">
  `true` after billing settles as charged.
</ResponseField>

<ResponseField name="chargedCredits" type="string">
  Settled credits charged.
</ResponseField>

<ResponseField name="billing" type="object">
  Planned and settled billing state.
</ResponseField>

<ResponseField name="request" type="object">
  Stable hash and exact sanitized payload.
</ResponseField>

<ResponseField name="account" type="object">
  Connected account selected for this write.
</ResponseField>

<ResponseField name="target" type="object | null">
  Target type and ID.
</ResponseField>

<ResponseField name="targetId" type="string | null">
  Target ID alias.
</ResponseField>

<ResponseField name="result" type="object | null">
  Confirmed result or desired state.
</ResponseField>

<ResponseField name="nextAction" type="object | null">
  Required poll, retry, or verification step.
</ResponseField>

<ResponseField name="requestHash" type="string">
  Stable request fingerprint.
</ResponseField>

<ResponseField name="requestId" type="string">
  Correlation ID echoed in `X-Request-Id`.
</ResponseField>

<ResponseField name="idempotent" type="boolean">
  `true` when this response answers a retry with the same `Idempotency-Key`.
</ResponseField>

<ResponseField name="error" type="string">
  Machine-readable error code.
</ResponseField>

<ResponseField name="message" type="string">
  Actionable status or error message.
</ResponseField>

<ResponseField name="sendDispatched" type="boolean">
  This field is `true` after dispatch.
</ResponseField>

<ResponseField name="sendDispatchedAt" type="string">
  ISO 8601 dispatch time.
</ResponseField>

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

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

<ResponseField name="completedAt" type="string">
  ISO 8601 terminal time.
</ResponseField>

<ResponseField name="expiresAt" type="string">
  Nonterminal resolution deadline.
</ResponseField>

<ResponseField name="confirmedAt" type="string">
  ISO 8601 confirmation time.
</ResponseField>

<ResponseField name="confirmationCheckedAt" type="string">
  ISO 8601 latest confirmation check.
</ResponseField>

<ResponseField name="confirmationAttempts" type="number">
  Confirmation attempt count.
</ResponseField>

<ResponseField name="tweetId" type="string">
  Confirmed tweet ID when available.
</ResponseField>

<ResponseField name="messageId" type="string">
  Confirmed direct message ID when available.
</ResponseField>

<ResponseField name="mediaId" type="string">
  Confirmed media ID when available.
</ResponseField>

<ResponseField name="mediaUrl" type="string">
  Public media URL when available.
</ResponseField>

<ResponseField name="communityId" type="string">
  Confirmed community ID when available.
</ResponseField>

<ResponseField name="communityName" type="string">
  Confirmed community name when available.
</ResponseField>

<ResponseField name="resultId" type="string">
  Result ID alias.
</ResponseField>

<ResponseField name="media" type="object">
  Media details when used.
</ResponseField>

<ResponseField name="details" type="object">
  Structured recovery context.
</ResponseField>

<ResponseField name="success" type="boolean">
  This field is `true` when status equals `success`.
</ResponseField>

```json theme={null}
{
  "object": "x_write_action",
  "id": "42",
  "writeActionId": "42",
  "action": "create_tweet",
  "status": "pending_confirmation",
  "terminal": false,
  "retryable": false,
  "safeToRetry": false,
  "statusUrl": "/api/v1/x/write-actions/42",
  "pollAfterMs": 2000,
  "billing": {
    "status": "pending",
    "charged": false,
    "plannedCredits": "30",
    "chargedCredits": "0"
  },
  "request": {
    "hash": "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb",
    "payload": { "text": "Hello" }
  },
  "account": { "id": "8", "username": "agent_account" },
  "target": null,
  "result": null,
  "nextAction": {
    "type": "poll",
    "url": "/api/v1/x/write-actions/42",
    "afterMs": 2000
  },
  "sendDispatched": true,
  "success": false
}
```

## Retry rules

* `retryable: false`, `safeToRetry: false`: do not retry.
* `retryable: true`, `safeToRetry: false`: verify the result first.
* `safeToRetry: true`: request approval and retry using a new key.
* `terminal: false`: poll, even when the original HTTP response was an error.

Xquik keeps idempotency replay protection for at least 90 days.


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