# Publish to X with the API
URL: https://skedcast.com/developers/guides/publish-to-x-api

X uses the same POST /v1/posts call. The x overrides cover quoting another post, who can reply, AI-media disclosure, paid partnership and a first-comment reply.

## What do you need before you publish?
You need one connected account for the platform. Connect it in the SkedCast console. If a client owns the account, create a connect link with `POST /v1/clients/{id}/connect-invites` and send it to them. They authorize from that page without a SkedCast login.
`GET /v1/accounts` lists the connected accounts with their ids. Pass that id in `targets.accountIds` below.

## How do you schedule a X post with the API?
```bash
curl -X POST https://api.skedcast.com/v1/posts \
  -H "Authorization: Bearer sked_live_YOUR_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "<clientId>",
    "content": "Launch day is here 🚀",
    "mediaIds": ["<mediaAssetId>"],
    "targets": { "mode": "selection", "accountIds": ["<xAccountId>"] },
    "schedule": { "type": "at", "scheduledAt": "2030-01-15T15:00:00Z" }
  }'
```

## Which X fields can you set?
Put everything platform-specific in one `variants` entry with `"platform": "x"` and an `overrides` object. Send only `x` keys on a `x` variant. SkedCast reads the keys in the table below and ignores the rest.

## Reference
Field | Type | Values / default | Notes
`quoteTweetId` | string | — | The id of the post to quote.
`replySettings` | enum | `following` \| `mentionedUsers` \| `subscribers` \| `verified` | Omit for X's default (everyone can reply).
`madeWithAi` | boolean | `false` | Discloses AI-generated or altered media. Independent of the post-level `aiDisclosure` field.
`paidPartnership` | boolean | `false` | Discloses a paid partnership or branded content.
`firstComment` | string | — | Posted as a reply from the same account right after publish. Best-effort: a failed comment never fails the post. The outcome is reported as `firstCommentIssue` on `GET /v1/post-targets/{id}`.
`pollOptions` | string[] (or a comma-separated string) | 2–4, up to 25 characters each | Turns the post into a poll. A poll cannot be combined with media.
`pollDurationMinutes` | integer | 5–10,080 (default 1,440) | How long the poll stays open, in minutes.

## Unknown keys are ignored without an error
A misspelled or unknown key in `overrides` is not an error and not a warning. It is dropped, so check the field names in the table above, especially a required one.
A YouTube-only key (such as `privacyStatus`, `captions` or `chapters`) sent on another platform's variant is ignored and returns an `override_key_wrong_platform` warning.

## How do you add X-specific settings?
```json
{
  "clientId": "<clientId>",
  "content": "Shipped: bulk import now handles 5,000-row CSVs.",
  "mediaIds": [],
  "targets": { "mode": "selection", "accountIds": ["<xAccountId>"] },
  "variants": [
    { "platform": "x", "overrides": { "replySettings": "following" } }
  ],
  "schedule": { "type": "at", "scheduledAt": "2030-01-15T15:00:00Z" }
}
```

## How do you check that the post published?
Publishing creates one post target per account. `GET /v1/post-targets/{id}` (or `GET /v1/posts/{id}`) reports `status`, plus `externalId` (X's own post id) and `permalink` (its public URL) once published. To avoid polling, subscribe to the `target.published` and `target.failed` webhooks (see the webhooks guide).

## FAQ
**Q: How do I post several connected posts as a thread?**
A: Use the post-level thread field: { segments: [{ content, mediaIds }, …] } with 2–50 entries. Segment 0 is the head post and every later segment replies to the one before it. On thread-capable platforms (X, Bluesky and Threads) the top-level content is ignored in favor of segments.
