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

# Validate a post

> Check a post against every addressed network, and see how it will be threaded.



## OpenAPI

````yaml /openapi.yaml post /v1/validate
openapi: 3.1.0
info:
  title: Nylon API
  version: 1.0.0
  description: |
    One API for publishing to twelve social networks.

    You address a post to connected profile ids; Nylon knows which network each
    one belongs to, what that network accepts, and how to get the media there.
    Character limits, media specs, upload protocols and threading rules are
    normalised before the request reaches a platform, and every failure comes
    back in one error taxonomy.

    ## Conventions

    - Request and response fields are `snake_case`.
    - Successful responses wrap the payload in `data`. List responses add
      `meta.pagination`.
    - Failures return `{ "error": { "code", "message", "details?" } }`.
      Branch on `code`; `message` is written for humans and may change.
    - Times are ISO 8601 with a `Z` offset.
    - Successful responses carry `RateLimit-Limit`, `RateLimit-Remaining` and
      `RateLimit-Reset`. A `429` carries those plus `Retry-After`.
servers:
  - url: https://api.nylon.dev
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Posts
    description: Create, schedule, inspect, edit and retry posts.
  - name: Profiles
    description: Social profiles connected to the authenticated Nylon account.
  - name: Connections
    description: Start a connection and see which networks are available.
  - name: Networks
    description: What each network accepts, as data.
  - name: Validation
    description: Dry-run a post without publishing it.
  - name: Webhooks
    description: Endpoints Nylon calls when a post finishes or a profile stops working.
paths:
  /v1/validate:
    post:
      tags:
        - Validation
      summary: Validate a post
      description: |
        A dry run of `POST /v1/posts`. Same body, same validation, nothing
        published.

        It exists because the two things worth knowing before committing —
        will this be rejected anywhere, and how will my caption be split into
        a thread — are both already computed, and neither is worth discovering
        by publishing.
      operationId: validatePost
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePostRequest'
            examples:
              longCaption:
                summary: A caption longer than one post on X
                value:
                  profile_ids:
                    - cly7p2q4k0003x8b3j6k2f0cc
                  text: >-
                    A caption well past two hundred and eighty characters, which
                    Nylon will split into a thread rather than reject.
      responses:
        '200':
          description: |
            The verdict. `valid` is false when any addressed network rejects
            the post; the response is still `200`, because the request itself
            was well formed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidateResponse'
              examples:
                threaded:
                  value:
                    data:
                      valid: true
                      targets:
                        - profile_id: cly7p2q4k0003x8b3j6k2f0cc
                          network: x
                          profile_name: Nylon
                          valid: true
                          thread_parts:
                            - >-
                              A caption well past two hundred and eighty
                              characters, which Nylon will
                            - split into a thread rather than reject.
                          errors: []
                      errors: []
                invalid:
                  value:
                    data:
                      valid: false
                      targets:
                        - profile_id: cly7p2q4k0001x8b3f2n9d0aa
                          network: instagram
                          profile_name: Nylon Studio
                          valid: false
                          thread_parts: null
                          errors:
                            - code: invalid_request
                              message: >-
                                Instagram requires at least 1 media item —
                                text-only posts are not supported.
                      errors:
                        - network: instagram
                          code: invalid_request
                          message: >-
                            Instagram requires at least 1 media item — text-only
                            posts are not supported.
        '400':
          $ref: '#/components/responses/InvalidRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '404':
          description: One of the `profile_ids` is not a profile on this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  schemas:
    CreatePostRequest:
      type: object
      required:
        - profile_ids
      properties:
        profile_ids:
          type: array
          minItems: 1
          description: >-
            The connected profiles to publish to. Every one is validated before
            anything sends.
          items:
            type: string
          example:
            - cly7p2q4k0001x8b3f2n9d0aa
        text:
          type: string
          maxLength: 20000
          default: ''
          description: |
            The caption. On the four threading networks this may exceed one
            post's limit — see `auto_thread`.
        media:
          type: array
          maxItems: 35
          default: []
          items:
            $ref: '#/components/schemas/MediaInput'
        link:
          description: A link to attach, on the networks that render one.
          oneOf:
            - $ref: '#/components/schemas/LinkInput'
            - type: 'null'
        post_type:
          type: string
          enum:
            - post
            - reel
            - story
          default: post
        title:
          type:
            - string
            - 'null'
          maxLength: 300
          description: Required by YouTube; used by Pinterest and TikTok photo posts.
        thread:
          type:
            - array
            - 'null'
          maxItems: 50
          items:
            type: string
          description: |
            Explicit thread parts. When set they are published verbatim rather
            than derived from `text` — someone who wrote the breaks gets the
            breaks they wrote.
        auto_thread:
          type: boolean
          default: true
          description: |
            Split an over-long caption into a thread rather than rejecting it.
            Only meaningful on X, Threads, Bluesky and Mastodon.
        scheduled_at:
          type:
            - string
            - 'null'
          format: date-time
          description: |
            When to publish. A time in the past is treated as now rather than
            refused, and so is a time less than a minute out — "in forty
            seconds" means now. Anything further out is held by a durable run
            that wakes at that time. The response says which happened:
            `status: "scheduled"` with the time, or an already-published post.
        publish_now:
          type: boolean
          description: Publish immediately, ignoring `scheduled_at`.
        idempotency_key:
          type:
            - string
            - 'null'
          minLength: 8
          maxLength: 255
          description: |
            Replaying a request with a key that was already used returns the
            original post untouched, with `Idempotent-Replay: true`.
        networks:
          type: object
          description: >-
            Per-network overrides, keyed by network. An unknown key is rejected
            by name.
          additionalProperties:
            $ref: '#/components/schemas/NetworkOverride'
    ValidateResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - valid
            - targets
            - errors
          properties:
            valid:
              type: boolean
              description: True only when every addressed network accepts the post.
            targets:
              type: array
              items:
                $ref: '#/components/schemas/ValidateTarget'
            errors:
              type: array
              description: Every issue across every network, flattened.
              items:
                $ref: '#/components/schemas/ErrorDetail'
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/Error'
    MediaInput:
      description: A URL, or an object when you want alt text or a video cover.
      oneOf:
        - type: string
          format: uri
          example: https://cdn.example.com/launch.jpg
        - type: object
          required:
            - url
          properties:
            url:
              type: string
              format: uri
            alt:
              type:
                - string
                - 'null'
              maxLength: 1000
              description: Alt text, on the networks that accept it.
            thumbnail_url:
              type:
                - string
                - 'null'
              format: uri
              description: Cover image for a video, where the network accepts one.
            mime_type:
              type:
                - string
                - 'null'
              description: Skips the probe when the host will not answer a HEAD request.
    LinkInput:
      description: A URL, or an object to control the preview card.
      oneOf:
        - type: string
          format: uri
        - type: object
          required:
            - url
          properties:
            url:
              type: string
              format: uri
            title:
              type:
                - string
                - 'null'
            description:
              type:
                - string
                - 'null'
            image_url:
              type:
                - string
                - 'null'
              format: uri
    NetworkOverride:
      type: object
      description: |
        Per-network overrides. The content keys below replace the base values
        for that network only; every other key is passed through to the
        publisher, because the option set differs per network. `GET
        /v1/networks` lists the options each one reads.
      additionalProperties: true
      properties:
        text:
          type:
            - string
            - 'null'
        title:
          type:
            - string
            - 'null'
        media:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/MediaInput'
        thread:
          type:
            - array
            - 'null'
          items:
            type: string
        post_type:
          type:
            - string
            - 'null'
          enum:
            - post
            - reel
            - story
            - null
      example:
        board_id: '881573081235'
        title: Launch day
    ValidateTarget:
      type: object
      required:
        - profile_id
        - network
        - profile_name
        - valid
        - errors
      properties:
        profile_id:
          type: string
        network:
          $ref: '#/components/schemas/Network'
        profile_name:
          type: string
        valid:
          type: boolean
        thread_parts:
          type:
            - array
            - 'null'
          description: |
            How the caption will be split on this network. Null when the
            content is invalid — a caption the splitter could not fit is
            already reported as an error.
          items:
            type: string
        errors:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              message:
                type: string
    ErrorDetail:
      type: object
      required:
        - message
      properties:
        field:
          type: string
          description: The request field the problem is about.
          example: profile_ids
        network:
          type: string
          description: The network the problem is about.
          example: instagram
        code:
          type: string
          description: |
            Machine-readable identifier for this problem. On a per-network
            detail this is a publishing code: `invalid_request`,
            `unsupported`, `media_error`, `reauthentication_required`,
            `rejected_by_network`, `network_error`, `timeout`,
            `profile_unavailable` or `internal_error`.
          example: invalid_request
        message:
          type: string
          example: >-
            Instagram requires at least 1 media item — text-only posts are not
            supported.
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: The stable identifier to branch on.
          enum:
            - invalid_request
            - unauthorized
            - payment_required
            - forbidden
            - not_found
            - conflict
            - rate_limited
            - unsupported
            - publish_failed
            - internal_error
          example: unauthorized
        message:
          type: string
          description: Written for humans. Do not match on it.
          example: A valid Nylon API key is required.
        details:
          type: array
          description: Present when the failure has a per-field or per-network breakdown.
          items:
            $ref: '#/components/schemas/ErrorDetail'
    Network:
      type: string
      description: A network Nylon publishes to.
      enum:
        - facebook
        - instagram
        - x
        - linkedin
        - pinterest
        - bluesky
        - threads
        - tiktok
        - youtube
        - google_business
        - mastodon
        - discord
      example: instagram
  responses:
    InvalidRequest:
      description: The request body or query is not valid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            badField:
              value:
                error:
                  code: invalid_request
                  message: The request body is not valid.
                  details:
                    - field: profile_ids
                      message: Address the post to at least one profile.
    Unauthorized:
      description: The bearer API key is missing, malformed or revoked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            missingKey:
              value:
                error:
                  code: unauthorized
                  message: A valid Nylon API key is required.
    PaymentRequired:
      description: |
        Billing is blocking the action. Most often an addressed profile is
        beyond the free allowance while the subscription is inactive — it is
        still connected and still listed with `suspended: true`, but nothing
        can be published through it until billing restarts.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            suspended:
              value:
                error:
                  code: payment_required
                  message: >-
                    Nylon Studio is beyond your free allowance and your
                    subscription is not active. Restart billing to publish
                    through them again.
    RateLimited:
      description: |
        Too many requests for this API key. Reads allow 120 requests a minute;
        publishing endpoints allow 30, because each one costs real upstream
        calls. The limit is per key, not per IP.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            throttled:
              value:
                error:
                  code: rate_limited
                  message: Too many requests. Retry after the window resets.
    InternalError:
      description: Nylon encountered an unexpected error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  headers:
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        example: 47
    RateLimitLimit:
      description: Requests allowed in the current window.
      schema:
        type: integer
        example: 120
    RateLimitRemaining:
      description: Requests left in the current window.
      schema:
        type: integer
        example: 119
    RateLimitReset:
      description: Seconds until the window resets.
      schema:
        type: integer
        example: 47
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Nylon API key
      description: An API key beginning with `nylon_live_`.

````