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

# Validate a campaign call

> Dry-run a webhook campaign send with full checks and a pricing quote, without delivery or charge.

A passing result proves that the key, campaign state, template, parameters, and media are accepted. It does not check the recipient's consent or the wallet balance, so a live [send](/docs/developer/api/webhook-campaigns/send) can still fail with `consent_revoked` or `insufficient_balance`. Validate calls are not rate limited. The **Validate** tab on the campaign page runs the same dry run from a form.

<ParamField path="campaignId" type="string" required>
  The webhook campaign ID. The **Contract** tab on the campaign page shows the full URL for your campaign.
</ParamField>

<ParamField body="to" type="string" required>
  Recipient phone number in international format with the leading `+` and country code, for example `+15551234567`. Any other format fails with `invalid_params` and an `errors` entry of `{ "field": "to", "code": "invalid_e164" }`.
</ParamField>

<ParamField body="params" type="object">
  Template parameters as string values, keyed by parameter name. Required when the campaign's template has parameters.
</ParamField>

<ParamField body="media" type="object">
  `{ "url": "…" }`. Required when the template has a media header. Tars fetches the URL and checks it against the header's media type.
</ParamField>

## Response

<ResponseField name="ok" type="boolean">
  `true` when the call passes every check that validation runs.
</ResponseField>

<ResponseField name="pricing" type="object">
  The pricing quote, present on a passing validation. Contains `category`, `quoted_usd`, and `currency`.
</ResponseField>

<ResponseField name="code" type="string">
  Machine-readable error code, present on a failing validation with the matching status.
</ResponseField>

<ResponseField name="errors" type="array">
  Per-field validation failures, present on a failing validation and empty when no single field is at fault. Each entry is `{ "field": "…", "code": "…" }`. The codes are `template_param_missing`, `template_param_invalid`, `unknown_param`, `unsupported_template`, `invalid_e164`, and `invalid_media`.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST \
    https://us.api.hellotars.com/api/campaigns/YOUR_CAMPAIGN_ID/validate \
    -H 'Authorization: Bearer YOUR_API_KEY' \
    -H 'Content-Type: application/json' \
    -d '{ "to": "+15551234567", "params": { "order_id": "A-1042" } }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://us.api.hellotars.com/api/campaigns/YOUR_CAMPAIGN_ID/validate",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.TARS_API_KEY}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        to: "+15551234567",
        params: { order_id: "A-1042" }
      })
    }
  );

  const data = await response.json();
  ```

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

  response = requests.post(
      "https://us.api.hellotars.com/api/campaigns/YOUR_CAMPAIGN_ID/validate",
      headers={
          "Authorization": f"Bearer {os.environ['TARS_API_KEY']}",
          "Content-Type": "application/json",
      },
      json={"to": "+15551234567", "params": {"order_id": "A-1042"}},
  )

  data = response.json()
  ```
</RequestExample>

<ResponseExample>
  ```json Passing theme={null}
  {
    "ok": true,
    "pricing": { "category": "marketing", "quoted_usd": 0.0025, "currency": "USD" }
  }
  ```

  ```json Failing theme={null}
  {
    "ok": false,
    "code": "invalid_params",
    "errors": [{ "field": "order_id", "code": "template_param_missing" }]
  }
  ```
</ResponseExample>

## Errors

Validation reports failures with the `ok`, `code`, and `errors` shape above, not the problem details document that `/send` returns. The status matches the [error catalog](/docs/developer/api/webhook-campaigns#errors-rfc-9457-problem-details), so `campaign_paused` answers `409` and `campaign_gone` answers `410`.

Two groups of failures still return problem details, because they happen before validation runs. These are `401` with `unauthorized` or `invalid_key`, and `500` with `internal_error`.


## OpenAPI

````yaml api-reference/openapi.json POST /api/campaigns/{campaignId}/validate
openapi: 3.1.0
info:
  title: Tars API
  version: 3.0.0
  description: >-
    Public REST API for Tars agents: start conversations at trigger gambits,
    send channel messages, manage end users, and drive webhook campaigns.
servers:
  - url: https://us.api.hellotars.com
    description: United States
  - url: https://eu.api.hellotars.com
    description: European Union
  - url: https://in.api.hellotars.com
    description: India
  - url: https://qa.api.hellotars.com
    description: Qatar
security:
  - bearerAuth: []
tags:
  - name: Triggers
    description: Start conversations at trigger gambits
  - name: Channels
    description: Outbound channel message sends
  - name: End users
    description: End-user record management
  - name: Campaigns
    description: Webhook campaign sends and validation
paths:
  /api/campaigns/{campaignId}/validate:
    post:
      tags:
        - Campaigns
      summary: Validate a webhook campaign call without sending
      description: >-
        Runs the campaign, channel, template, and parameter checks and the
        pricing quote that send runs, without delivering or charging. It does
        not check end user consent or the wallet balance.
      operationId: validateCampaignCall
      parameters:
        - name: campaignId
          in: path
          required: true
          description: >-
            The campaign's ID. The campaign page's Contract tab shows the full
            URL for your campaign.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignRequest'
      responses:
        '200':
          description: The call would succeed
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    const: true
                    description: True when the same call would be accepted by send.
                  pricing:
                    type: object
                    description: >-
                      What the send would cost. A validate call never charges
                      the wallet.
                    properties:
                      category:
                        type: string
                        enum:
                          - marketing
                          - utility
                          - authentication
                        description: The template's pricing category, which sets the rate.
                      quoted_usd:
                        type: number
                        description: The quoted cost in US dollars.
                      currency:
                        type: string
                        const: USD
                        description: Always USD.
                    required:
                      - category
                      - quoted_usd
                      - currency
                required:
                  - ok
                  - pricing
        '400':
          description: Not a webhook campaign (campaign_mode_invalid)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignValidationFailure'
        '401':
          description: unauthorized or invalid_key
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
        '403':
          description: >-
            Marketing template to a US number, or a channel account in test mode
            (us_marketing_blocked, channel_test_mode)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignValidationFailure'
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignValidationFailure'
        '409':
          description: >-
            Campaign paused or in draft, channel account not active, or template
            changed since launch (campaign_paused, channel_unavailable,
            template_version_drift)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignValidationFailure'
        '410':
          description: Campaign cancelled or failed (campaign_gone)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignValidationFailure'
        '422':
          description: >-
            Validation failed: invalid_params (see errors),
            template_not_approved, or channel_misconfigured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignValidationFailure'
        '500':
          description: internal_error
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
components:
  schemas:
    CampaignRequest:
      type: object
      description: One webhook campaign send.
      properties:
        to:
          type: string
          description: Recipient phone number in international format
        params:
          type: object
          additionalProperties:
            type: string
          description: Template parameters keyed by slot name
        media:
          type: object
          description: >-
            Media for the template's media slot. Sending it to a template with
            no media slot returns unknown_param on media.url.
          properties:
            url:
              type: string
              description: Publicly reachable URL of the media file.
          required:
            - url
      required:
        - to
    CampaignValidationFailure:
      type: object
      description: >-
        A dry run that found problems. Nothing was sent and the wallet was not
        charged.
      properties:
        ok:
          type: boolean
          const: false
          description: Always false when validation fails.
        code:
          type: string
          description: Error code from the webhook error catalog
        errors:
          type: array
          description: Every field problem the dry run found.
          items:
            $ref: '#/components/schemas/CampaignFieldError'
      required:
        - ok
        - errors
    Problem:
      type: object
      description: RFC 9457 problem details, returned by the webhook campaign endpoints.
      properties:
        title:
          type: string
          description: A short summary of the problem type.
        status:
          type: integer
          description: The HTTP status code, repeated in the body.
        detail:
          type: string
          description: A sentence describing this specific failure.
        instance:
          type: string
          description: The tars_… request ID for log correlation
        code:
          type: string
          description: A stable machine-readable code from the campaign error catalog.
          enum:
            - not_found
            - unauthorized
            - invalid_key
            - organization_inactive
            - billing_locked
            - rate_limited
            - invalid_params
            - campaign_mode_invalid
            - campaign_paused
            - campaign_gone
            - template_version_drift
            - template_not_approved
            - consent_revoked
            - us_marketing_blocked
            - channel_misconfigured
            - channel_unavailable
            - channel_reauth_required
            - channel_test_mode
            - insufficient_balance
            - meta_rejected
            - meta_rate_limited
            - meta_unavailable
            - internal_error
        retryable:
          type: boolean
          description: true when retrying the same call can succeed
        errors:
          type: array
          description: Per-field problems, when the call failed validation.
          items:
            $ref: '#/components/schemas/CampaignFieldError'
      required:
        - title
        - status
        - detail
        - instance
        - code
        - retryable
    CampaignFieldError:
      type: object
      description: One field that failed campaign validation.
      properties:
        field:
          type: string
          description: >-
            The request field that failed, for example params.order_id or
            media.url.
        code:
          type: string
          description: Why the field failed.
          enum:
            - template_param_missing
            - template_param_invalid
            - unsupported_template
            - unknown_param
            - invalid_e164
            - invalid_media
      required:
        - field
        - code
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key sent as Authorization: Bearer YOUR_API_KEY. Campaign endpoints
        use campaign-scoped keys; the rest use organization or agent keys with
        the matching permission.

````