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

# Post apiv1campaignsad copyvalidate

> Checks a marked-up ad text against exactly the rules campaign creation will apply to it, and answers with everything that is wrong rather than the first problem found. The WYSIWYG editor on the front end serialises its state into our markup and calls this on every pause in typing: there is no second parser anywhere, so this endpoint and campaign creation cannot disagree by construction. A text that is wrong is still a successful call — the verdict is in the body, and a 4xx here means the request itself was malformed (an unusable destinationUrl, a missing field).



## OpenAPI

````yaml https://backend.aflux.ai/openapi post /api/v1/campaigns/ad-copy/validate
openapi: 3.0.3
info:
  title: AdFlux Backend API
  version: v1
servers:
  - url: /
security: []
tags:
  - name: auth
  - name: users
  - name: uploads
  - name: projects
  - name: campaigns
  - name: placements
  - name: billing
  - name: payments
  - name: image-generations
paths:
  /api/v1/campaigns/ad-copy/validate:
    post:
      tags:
        - campaigns
      description: >-
        Checks a marked-up ad text against exactly the rules campaign creation
        will apply to it, and answers with everything that is wrong rather than
        the first problem found. The WYSIWYG editor on the front end serialises
        its state into our markup and calls this on every pause in typing: there
        is no second parser anywhere, so this endpoint and campaign creation
        cannot disagree by construction. A text that is wrong is still a
        successful call — the verdict is in the body, and a 4xx here means the
        request itself was malformed (an unusable destinationUrl, a missing
        field).
      operationId: campaignsValidateAdCopy
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidateAdCopyRequest'
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidateAdCopyResponse'
        default:
          $ref: '#/components/responses/DefaultError'
      security:
        - Bearer: []
components:
  schemas:
    ValidateAdCopyRequest:
      type: object
      required:
        - text
        - destinationUrl
      properties:
        text:
          type: string
          maxLength: 8000
          description: >-
            The ad text as the advertiser wrote it, markup included. Formatting
            is written the way Telegram writes it — *bold*, _italic_,
            __underline__, ~strikethrough~, ||spoiler||, `monospace`, >quote to
            the end of the line — plus two of ours: `--` becomes an em dash, and
            {these words} become the post's link to destinationUrl. A premium
            custom emoji is written `![😀](tg://emoji?id=5368324170671202286)`:
            the emoji with that document id is drawn over the character in the
            brackets, which has to be a single emoji itself — it is what a
            reader whose client cannot resolve the id goes on seeing, and it is
            what the visible length counts. Only the ten characters that mean
            something need escaping (\* \_ \~ \` \| \{ \} \> \- \\); full stops,
            brackets and percent signs are plain text, unlike Bot API
            MarkdownV2. An exclamation mark is plain text too, everywhere except
            in front of a `[`, where it would begin the custom-emoji form: `\!`
            there is the character itself, and it is how a text carrying
            `![😀](tg://emoji?id=...)` as literal characters is written down.
            Inside a recognised web address none of them mean anything at all:
            `mysite.com/my_page_2` keeps both underscores, `/a--b` keeps both
            hyphens and `?utm_campaign={campaign}` keeps its braces, so nobody
            has to escape his own URL. The cap here is a bound on work per
            request, an order of magnitude above any text that could pass the
            700-character visible limit; exceeding it is a 400, not a validation
            error.
        destinationUrl:
          type: string
          minLength: 1
          maxLength: 2048
          description: >-
            Where the campaign's link goes. Required because it is what defines
            the advertiser's own domain, and the rule that no other domain may
            appear in the text cannot be evaluated without it. Accepted with or
            without a scheme while the advertiser is still typing the form
            field; anything that is not a URL either way is a 400.
    ValidateAdCopyResponse:
      type: object
      required:
        - valid
        - visibleLength
        - errors
      properties:
        valid:
          type: boolean
          description: >-
            True only when errors is empty. Campaign creation will accept
            exactly the texts this is true for
        visibleLength:
          type: integer
          format: int32
          description: >-
            Length of what the reader will see — markup characters removed, `--`
            already an em dash — counted in UTF-16 code units, so an emoji
            counts as 2. This is the number the 700 limit applies to and the
            number an editor should put in its character counter
        errors:
          type: array
          items:
            $ref: '#/components/schemas/AdCopyError'
    AdCopyError:
      type: object
      required:
        - offset
        - length
        - code
        - message
      description: >-
        One problem, positioned so an editor can underline it. offset and length
        address the marked-up string that was sent, NOT the visible text,
        because that is where the mistake is: the visible text does not contain
        the '*' that was never closed. Both are in UTF-16 code units.
      properties:
        offset:
          type: integer
          format: int32
        length:
          type: integer
          format: int32
        code:
          $ref: '#/components/schemas/AdCopyErrorCode'
        message:
          type: string
          description: >-
            English prose for a human. Branch on code, never on this — the
            wording is free to change
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: string
    AdCopyErrorCode:
      type: string
      description: >-
        UNCLOSED_ENTITY - a delimiter was opened and the text ended first.
        AMBIGUOUS_DELIMITER - '___', which is '__'+'_' or '_'+'__' with nothing
        to choose between them. OVERLAPPING_ENTITIES - a closer belongs to a
        section that is not the innermost open one; styles must nest.
        EMPTY_ENTITY - a delimiter pair with nothing between it. EMPTY_LINK -
        '{}' with no anchor text. INVALID_ESCAPE - a backslash before a
        character that is not escapable. NESTED_LINK - '{' inside another
        '{...}'. UNMATCHED_LINK_CLOSE - '}' with no '{' open.
        INVALID_CUSTOM_EMOJI_FALLBACK - the '[x]' of '![x](tg://emoji?id=...)'
        is not a single emoji. The custom emoji is drawn over that character,
        and every reader whose client cannot resolve it goes on seeing exactly
        what stands there, so a letter, a digit, a word, a space or nothing at
        all is refused. INVALID_CUSTOM_EMOJI_ID - the id of
        '![x](tg://emoji?id=...)' is not an unsigned whole number that fits in
        64 bits. LINK_REQUIRED - the text contains no '{...}' at all.
        FOREIGN_LINK - a URL or domain that is not the advertiser's own or a
        subdomain of it. TEXT_TOO_LONG - the visible text is over 700 units.
      enum:
        - UNCLOSED_ENTITY
        - AMBIGUOUS_DELIMITER
        - OVERLAPPING_ENTITIES
        - EMPTY_ENTITY
        - EMPTY_LINK
        - INVALID_ESCAPE
        - NESTED_LINK
        - UNMATCHED_LINK_CLOSE
        - INVALID_CUSTOM_EMOJI_FALLBACK
        - INVALID_CUSTOM_EMOJI_ID
        - LINK_REQUIRED
        - FOREIGN_LINK
        - TEXT_TOO_LONG
  responses:
    DefaultError:
      description: Error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT

````