> ## Documentation Index
> Fetch the complete documentation index at: https://docs.superx.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Queue a DM campaign

> Queues direct messages to up to 100 X users.

NOTHING IS SENT BY THIS CALL. The messages go into the account's own
DM queue, the same one the SuperX app fills, and the app's scheduler
sends them within the account's daily and monthly DM limits. The 201
is therefore COUNTS, not deliveries: read
`GET /v1/dm/campaigns/{id}` to see what has actually gone out.

`[name]`, `[first]` and `[handle]` in the message are filled in per
recipient. A recipient may carry its own `message`, which wins over
the shared one; when every recipient has one, the top-level `message`
may be omitted.

People this account already messaged in the last 24 hours are skipped
and counted in `duplicates`, and the sending account is never
messaged. `spread: false` (the default) queues only what today's
remaining daily allowance holds and skips the rest; `spread: true`
places the rest over the coming days, up to 30 days ahead.

When EVERY recipient is skipped, nothing is queued: the answer is
`200` with `data.id: null` and a note saying so, rather than a `201`
carrying an id that would resolve to nothing.

Costs no AI credits. It counts against the writes rate-limit bucket
and against the account's own DM allowances
(`GET /v1/dm/limits`). Takes up to 30 seconds.

With an `Idempotency-Key`, the campaign id IS the reservation id, so a
retry after a timeout converges on the same campaign instead of
orphaning the rows the first attempt queued.

You are responsible for these messages under X's rules on automation
and unsolicited contact.




## OpenAPI

````yaml /api-reference/openapi.yaml post /v1/dm/campaigns
openapi: 3.1.0
info:
  title: SuperX API
  version: 1.0.0
  description: >
    The SuperX public API: your Twitter/X content, analytics, audience and

    scheduling data over REST.


    All endpoints require an API key (`Authorization: Bearer sxk_...`) except

    `GET /v1/docs`. Keys are created in the SuperX app under Account > API / MCP
    / CLI

    and are server-side secrets.


    Timestamps are UTC ISO-8601 in both directions; inputs must carry an
    explicit

    `Z` or numeric offset. Every authenticated response carries

    `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`
    headers.
  contact:
    name: SuperX
    url: https://superx.so
servers:
  - url: https://api.superx.so
security:
  - apiKey: []
tags:
  - name: Identity
  - name: Content
  - name: Analytics
  - name: Inspiration
  - name: Audience
  - name: Contact Lists
  - name: Signals
  - name: Datasets
    description: |
      The audience collections Ask SuperX builds in the app: the repliers,
      quoters or reposters of a post, the members of an X list, your own
      posts or replies, or a research brief.

      Collect a new one with `POST /v1/datasets` (a big collection answers
      202 and finishes in the background), read it, export it, or copy the
      people in it into a contact list you created. Datasets are kept for 30
      days, and the ones Ask SuperX builds in the app show up here too.
  - name: Engage
    description: |
      The account's saved Engage feeds and the candidate posts in them, for
      an agent to score and a person to act on.

      Read-only by design. These endpoints return feed candidates for a
      person to review; replies are written and sent by a person in the
      SuperX app, which is why there is no reply endpoint here. Sending
      spammy, automated, or AI-generated replies that read as inauthentic
      may get your X account suspended under X's inauthentic-behavior
      rules and your SuperX account terminated. AI suggestions must be
      reviewed and meaningfully edited before they are sent, and you are
      solely responsible for what you post. Reply activity is logged and
      may be audited.
  - name: X Lookups
    description: |
      Read any public post, its top replies, any public profile, or an
      account's latest posts LIVE from X, rather than from SuperX's stored
      data.

      These are owner-scoped, so none of them take an `account_id`: nothing
      about a public lookup is per-X-account. They cost the tighter
      live-enrichment allowance and share a daily allowance of 300 live
      lookups with Ask SuperX inside the app, so use them for what you
      actually need rather than to sweep. Repeat lookups within 15 minutes
      may be served from a short-lived server-side cache.
  - name: Tools
    description: |
      The composer helpers from the SuperX app, over the API: rewrite a post,
      edit one selected piece of it, apply one preset rewrite, check a claim,
      or compare two versions.

      Every one of them returns TEXT and writes nothing. Nothing here is
      posted, scheduled or sent; save what you keep with
      `POST /v1/posts/draft` or `POST /v1/scheduled-posts`. They cost AI
      credits, measured from the models' real cost, and spend no live-data
      allowance.
  - name: Media
  - name: Workers
    description: |
      The posts the account's Workers have written, and the three things a
      person does with one: save it as a draft, schedule it, or dismiss it.

      A Worker is a scheduled AI writer set up in the SuperX app. Creating,
      editing and running one stays in the app, so there is no create or run
      endpoint here. What a run produces lands in the app's "To review" queue
      and is read with `GET /v1/workers/suggestions`.

      NOTHING HERE POSTS TO X. Drafting or scheduling a suggestion writes a
      post the user can see in the app and on `GET /v1/scheduled-posts`,
      exactly like the app's own buttons. AI output must be reviewed and
      meaningfully edited by a person before it goes out, so show the text to
      the user and confirm the time before you schedule anything.
  - name: Scheduling
  - name: Tags
  - name: Context
  - name: Queue
  - name: DMs
    description: |
      Queue direct messages into the account's own DM pipeline, and read or
      cancel what is waiting.

      NOTHING HERE SENDS A MESSAGE. `POST /v1/dm/campaigns` enqueues; the
      SuperX app's scheduler is what sends, within the account's daily and
      monthly DM limits, so the create response is counts, not deliveries.
      Unsent messages can be cancelled until the scheduler picks them up.

      You are responsible for the messages you queue. Unsolicited or
      automated bulk DMs may get an X account suspended under X's rules and
      a SuperX account terminated, so confirm the recipient list and the
      exact wording with the person on whose behalf you are queueing.
  - name: Articles
  - name: Meta
paths:
  /v1/dm/campaigns:
    post:
      tags:
        - DMs
      summary: Queue a DM campaign
      description: |
        Queues direct messages to up to 100 X users.

        NOTHING IS SENT BY THIS CALL. The messages go into the account's own
        DM queue, the same one the SuperX app fills, and the app's scheduler
        sends them within the account's daily and monthly DM limits. The 201
        is therefore COUNTS, not deliveries: read
        `GET /v1/dm/campaigns/{id}` to see what has actually gone out.

        `[name]`, `[first]` and `[handle]` in the message are filled in per
        recipient. A recipient may carry its own `message`, which wins over
        the shared one; when every recipient has one, the top-level `message`
        may be omitted.

        People this account already messaged in the last 24 hours are skipped
        and counted in `duplicates`, and the sending account is never
        messaged. `spread: false` (the default) queues only what today's
        remaining daily allowance holds and skips the rest; `spread: true`
        places the rest over the coming days, up to 30 days ahead.

        When EVERY recipient is skipped, nothing is queued: the answer is
        `200` with `data.id: null` and a note saying so, rather than a `201`
        carrying an id that would resolve to nothing.

        Costs no AI credits. It counts against the writes rate-limit bucket
        and against the account's own DM allowances
        (`GET /v1/dm/limits`). Takes up to 30 seconds.

        With an `Idempotency-Key`, the campaign id IS the reservation id, so a
        retry after a timeout converges on the same campaign instead of
        orphaning the rows the first attempt queued.

        You are responsible for these messages under X's rules on automation
        and unsolicited contact.
      operationId: queueDmCampaign
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - recipients
              properties:
                recipients:
                  type: array
                  minItems: 1
                  maxItems: 100
                  items:
                    type: object
                    required:
                      - x_user_id
                    properties:
                      x_user_id:
                        type: string
                        pattern: ^[0-9]{1,25}$
                        description: The recipient's X user id (digits).
                      handle:
                        type: string
                        maxLength: 15
                        description: The recipient's @handle, used to fill `[handle]`.
                      name:
                        type: string
                        maxLength: 100
                        description: >-
                          The recipient's display name, used to fill `[name]`
                          and `[first]`.
                      message:
                        type: string
                        minLength: 1
                        maxLength: 1000
                        description: >-
                          This recipient's own message, instead of the shared
                          one.
                      source_post_id:
                        type: string
                        pattern: ^[0-9]{1,25}$
                        description: >-
                          The post this person was found on, so later imports of
                          it can skip them.
                message:
                  type: string
                  minLength: 1
                  maxLength: 1000
                  description: >-
                    The shared message. Required unless every recipient carries
                    its own.
                spread:
                  type: boolean
                  default: false
                  description: >-
                    Spread what today's daily allowance cannot hold over the
                    coming days instead of skipping it. Ignored when `GET
                    /v1/dm/limits` reports `scheduling_enabled: false`; the
                    overflow is then counted in `skipped`.
                account_id:
                  type: string
                  description: >-
                    Any account you own, meaning your main account (the default
                    when omitted) or one linked to it. An account shared with
                    you returns `403 writes_main_account_only`.
            example:
              recipients:
                - x_user_id: '44196397'
                  handle: levelsio
                  name: Pieter Levels
              message: Hey [first], loved your thread on shipping fast.
      responses:
        '200':
          description: >-
            Nothing was queued because every recipient was skipped. `data.id` is
            null.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/DmCampaign'
                  note:
                    type: string
              example:
                data:
                  id: null
                  queued: 0
                  queued_now: 0
                  scheduled: []
                  scheduled_total: 0
                  skipped: 2
                  duplicates: 2
                  remaining_this_month: 450
                note: >-
                  Every recipient was skipped (already queued in the last 24
                  hours, over quota, or the sender); nothing was queued.
        '201':
          description: The campaign was queued. Nothing has been sent.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            Idempotency-Replayed:
              description: >-
                Present and "true" when this response was replayed from a
                previous request with the same Idempotency-Key.
              schema:
                type: string
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/DmCampaign'
                  note:
                    type: string
                    description: A reminder that nothing was sent by this call.
              example:
                data:
                  id: V1StGXR8_Z5jdHi6B-myT
                  queued: 2
                  queued_now: 2
                  scheduled: []
                  scheduled_total: 0
                  skipped: 0
                  duplicates: 0
                  remaining_this_month: 448
                note: >-
                  Queued into your DM pipeline. Nothing is sent by this call;
                  the scheduler sends within your daily and monthly limits. You
                  are responsible for these messages under X's automation rules.
        '400':
          description: >-
            A parameter is invalid (`invalid_parameter`), or the account has no
            X account connected (`account_not_linked`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                error:
                  code: account_not_linked
                  message: >-
                    This account has no X account connected, so it cannot send
                    DMs.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            The key is read-only (`insufficient_scope`), the account is shared
            with you (`writes_main_account_only`), the plan has no DM allowance
            (`dm_not_in_plan`), or the X account must be reconnected in the app
            (`reauth_required`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                error:
                  code: dm_not_in_plan
                  message: This account's plan does not include DM sending.
        '404':
          $ref: '#/components/responses/AccountNotFound'
        '409':
          description: The Idempotency-Key was already used with a different request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                error:
                  code: idempotency_key_reuse
                  message: >-
                    This Idempotency-Key was already used with a different
                    request body.
        '429':
          $ref: '#/components/responses/DmLimitReached'
        '500':
          $ref: '#/components/responses/InternalError'
        '502':
          $ref: '#/components/responses/UpstreamError'
        '503':
          $ref: '#/components/responses/UpstreamUnavailable'
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >-
        Unique key (max 64 characters) for safe retries. Replays carry the
        "Idempotency-Replayed" response header set to "true". Keys are retained
        for 24 hours.
      schema:
        type: string
        maxLength: 64
  schemas:
    DmCampaign:
      type: object
      description: The result of queueing a campaign. Counts, not deliveries.
      properties:
        id:
          type: string
          nullable: true
          description: >-
            Campaign id. Use it with `GET`/`DELETE /v1/dm/campaigns/{id}` and as
            `campaign_id` on `GET /v1/dm/queue`. **null when nothing was
            queued** (every recipient skipped): no row carries an id, so there
            is none to hand back.
        queued:
          type: integer
          description: Messages placed in the queue by this call.
        queued_now:
          type: integer
          description: How many of those are due to go out in the current daily window.
        scheduled:
          type: array
          description: Per-day placement of the rest, when `spread` was true.
          items:
            type: object
            properties:
              day:
                type: string
                format: date
              count:
                type: integer
        scheduled_total:
          type: integer
        skipped:
          type: integer
          description: >-
            Recipients not queued (duplicates, the monthly cap, or the daily cap
            without `spread`).
        duplicates:
          type: integer
          description: >-
            Recipients skipped because this account messaged them in the last 24
            hours, or because they are the sending account.
        remaining_this_month:
          type: integer
          description: Messages left in the monthly allowance after this campaign.
    ErrorEnvelope:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Stable machine-readable error code.
            message:
              type: string
            retry_after:
              type: integer
              description: Seconds to wait before retrying (rate-limit errors only).
    LegacyErrorEnvelope:
      type: object
      required:
        - error
      description: |
        Legacy shape used ONLY by the shared subscription middleware: a plain
        string `error` field. Seen on `403` when the subscription has lapsed
        (string starts with "subscription_required:") and on `500` when
        subscription verification fails (string starts with "internal_error:").
      properties:
        error:
          type: string
          example: >-
            subscription_required: The SuperX API requires an active
            subscription
  headers:
    X-RateLimit-Limit:
      description: The limit of the rate window closest to exhaustion.
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests remaining in that window.
      schema:
        type: integer
    X-RateLimit-Reset:
      description: Unix timestamp (seconds) when that window resets.
      schema:
        type: integer
  responses:
    Unauthorized:
      description: >-
        Missing/malformed Authorization header (`unauthorized`) or an
        unknown/revoked key (`invalid_api_key`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: invalid_api_key
              message: Unknown or revoked API key
    AccountNotFound:
      description: account_id is not one of your accounts.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: account_not_found
              message: No account with that id belongs to this key
    DmLimitReached:
      description: >-
        The account's DM allowance is used up (`dm_limit_reached`). `scope` is
        `month` or `day`; the monthly refusal also carries `reset_at`. These are
        plan limits, not AI credits.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: dm_limit_reached
              message: This account's monthly DM allowance is used up.
              scope: month
              retry_after: 86400
              reset_at: '2026-10-01T00:00:00.000Z'
    InternalError:
      description: >-
        Unexpected server error. May also use the legacy string envelope when
        subscription verification fails.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/ErrorEnvelope'
              - $ref: '#/components/schemas/LegacyErrorEnvelope'
          example:
            error:
              code: internal_error
              message: Failed to fetch posts
    UpstreamError:
      description: A dependent SuperX service returned an unexpected response.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: upstream_error
              message: Failed to fetch scheduled posts. Try again shortly.
    UpstreamUnavailable:
      description: >-
        The scheduling service is temporarily unreachable. Safe to retry; reuse
        your Idempotency-Key on POST.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            error:
              code: upstream_unavailable
              message: >-
                The scheduling service is temporarily unavailable. Retry with
                the same Idempotency-Key.
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: >-
        A SuperX API key ("sxk_..."), created in the SuperX app under Account >
        API / MCP / CLI. Keys are server-side secrets.

````