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

# Fixing a Rejected or Changes-Needed Campaign

> How to read reviewer feedback, update a campaign in a valid status, and get back to production after rejection.

Campaign reviews are done by human reviewers, not automated systems. When they need something from you, the campaign status changes to `changes_needed` or `rejected`. Here's how to read the feedback and get your campaign approved.

## Campaign status lifecycle

```mermaid theme={null}
stateDiagram-v2
  [*] --> created
  created --> pending
  pending --> in_review
  in_review --> active
  in_review --> changes_needed
  in_review --> rejected
  changes_needed --> pending : resubmit
```

The diagram shows the typical review lifecycle. `deactivated` and `canceled` are administrative states reachable from `active` (and earlier states for `canceled`); see the list below.

* **`created`**: you've saved the campaign fields, but it hasn't been submitted to Surge yet
* **`pending`**: submitted, waiting for Surge to review it before sending it to the carriers
* **`in_review`**: waiting for the carriers to review it
* **`active`**: approved; you can attach phone numbers and send production traffic
* **`changes_needed`**: reviewer flagged issues; you need to update and resubmit
* **`rejected`**: campaign was rejected; see notes on appealing below
* **`deactivated`**: previously active campaign that you or Surge canceled
* **`canceled`**: you or Surge canceled the campaign
* **`suspended`**: previously active campaign suspended by carriers

<Note>
  **API status values.** The campaign `status` field in API responses uses a condensed set of public values: `created`, `in_review`, `active`, `rejected`, `canceled`, `deactivated`. Both the `pending` and `in_review` internal states surface as `"in_review"` in the API. Both the `changes_needed` and `rejected` internal states surface as `"rejected"`. Keep this in mind when polling for status or using the update table below.
</Note>

## Understanding "changes needed" feedback

<Tip>
  When a campaign moves to `changes_needed`, Surge sends you an email with reviewer notes. Read it carefully — reviewers include specific details about what needs to change.
</Tip>

Common patterns and what they mean:

| Reviewer says                                       | What to fix                                                                                                    |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| "The website doesn't give much info"                | Add clear business description and contact information to your homepage                                        |
| "The opt-in page doesn't show required disclosures" | Add message frequency notice, "Msg\&data rates apply", and "Reply STOP to opt out" next to the opt-in checkbox |
| "The sample message contains placeholder text"      | Replace `{brand name}`, `{Agent Name}`, etc. with actual values                                                |
| "The privacy policy is behind a login"              | Move the privacy policy to a publicly accessible URL                                                           |
| "Your business email doesn't match the domain"      | Use an email at your business domain, not a personal or generic address                                        |

## Updating a campaign

To address feedback, update the relevant campaign fields and resubmit. Campaigns can only be updated in certain statuses:

| Status (API value) | Can update?                                                                                                                                                                        |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `created`          | Yes                                                                                                                                                                                |
| `in_review`        | **Usually no**: returns `campaign_locked` (409) once a reviewer picks it up. A brief window exists while the campaign is still queued (internally `pending`) before review begins. |
| `rejected`         | Yes, both "changes needed" and hard-rejected campaigns can be updated.                                                                                                             |
| `canceled`         | Yes                                                                                                                                                                                |
| `active`           | **No**: returns `campaign_locked` (409)                                                                                                                                            |
| `deactivated`      | **No**: returns `campaign_locked` (409)                                                                                                                                            |

If you try to update while `in_review`, wait for the review to complete. The reviewer will either approve it, request changes (moving it to `changes_needed`), or reject it. Only then can you update.

<Warning>
  The campaign update endpoint requires the **full** field set, not a partial body. Send `consent_flow`, `description`, `message_samples` (≥2 items), `privacy_policy_url`, `terms_and_conditions_url`, `use_cases`, and `volume` together. Missing any field returns a `validation_error`.
</Warning>

<CodeGroup>
  ```python Python theme={null}
  from surge import Surge

  surge = Surge()  # reads SURGE_API_KEY from environment

  campaign = surge.campaigns.update(
      "{campaign_id}",
      consent_flow=(
          "Users enter their phone number during checkout. An unchecked checkbox "
          'labeled "I agree to receive text messages from Acme Corp" is present. '
          'Required disclosures are shown: "Message frequency varies. Msg&data '
          'rates apply. Reply STOP to opt out. View our Privacy Policy at '
          'acme.com/privacy."'
      ),
      message_samples=[
          "Acme Corp: Your order #12345 has shipped! Track it: https://acme.com/track",
          "Acme Corp: Your cart is waiting. Complete your order: https://acme.com/cart. Reply STOP to opt out.",
      ],
      description="Marketing messages to opted-in customers",
      privacy_policy_url="https://acme.com/privacy",
      terms_and_conditions_url="https://acme.com/terms",
      use_cases=["marketing"],
      volume="low",
  )

  print(campaign.status)  # "in_review"
  ```

  ```typescript TypeScript theme={null}
  import Surge from "@surgeapi/node";

  const surge = new Surge();

  const campaign = await surge.campaigns.update("{campaign_id}", {
    consent_flow:
      'Users enter their phone number during checkout. An unchecked checkbox ' +
      'labeled "I agree to receive text messages from Acme Corp" is present. ' +
      'Required disclosures are shown: "Message frequency varies. Msg&data ' +
      'rates apply. Reply STOP to opt out. View our Privacy Policy at ' +
      'acme.com/privacy."',
    message_samples: [
      "Acme Corp: Your order #12345 has shipped! Track it: https://acme.com/track",
      "Acme Corp: Your cart is waiting. Complete your order: https://acme.com/cart. Reply STOP to opt out.",
    ],
    description: "Marketing messages to opted-in customers",
    privacy_policy_url: "https://acme.com/privacy",
    terms_and_conditions_url: "https://acme.com/terms",
    use_cases: ["marketing"],
    volume: "low",
  });

  console.log(campaign.status); // "in_review"
  ```

  ```ruby Ruby theme={null}
  require "surge_api"

  surge = SurgeAPI::Client.new

  campaign = surge.campaigns.update(
    "{campaign_id}",
    consent_flow:
      'Users enter their phone number during checkout. An unchecked checkbox ' \
      'labeled "I agree to receive text messages from Acme Corp" is present. ' \
      'Required disclosures are shown: "Message frequency varies. Msg&data ' \
      'rates apply. Reply STOP to opt out. View our Privacy Policy at ' \
      'acme.com/privacy."',
    message_samples: [
      "Acme Corp: Your order #12345 has shipped! Track it: https://acme.com/track",
      "Acme Corp: Your cart is waiting. Complete your order: https://acme.com/cart. Reply STOP to opt out."
    ],
    description: "Marketing messages to opted-in customers",
    privacy_policy_url: "https://acme.com/privacy",
    terms_and_conditions_url: "https://acme.com/terms",
    use_cases: ["marketing"],
    volume: "low"
  )

  puts campaign.status # "in_review"
  ```

  ```elixir Elixir theme={null}
  # Surge.Campaigns.update/3 isn't typed in the Elixir SDK yet — use the raw client.
  client = Surge.Client.new(System.get_env("SURGE_API_KEY"))

  {:ok, campaign} =
    Surge.Client.request(client, :patch, "/campaigns/{campaign_id}",
      json: %{
        consent_flow:
          "Users enter their phone number during checkout. An unchecked checkbox " <>
            "labeled \"I agree to receive text messages from Acme Corp\" is present. " <>
            "Required disclosures are shown: \"Message frequency varies. Msg&data " <>
            "rates apply. Reply STOP to opt out. View our Privacy Policy at " <>
            "acme.com/privacy.\"",
        message_samples: [
          "Acme Corp: Your order #12345 has shipped! Track it: https://acme.com/track",
          "Acme Corp: Your cart is waiting. Complete your order: https://acme.com/cart. Reply STOP to opt out."
        ],
        description: "Marketing messages to opted-in customers",
        privacy_policy_url: "https://acme.com/privacy",
        terms_and_conditions_url: "https://acme.com/terms",
        use_cases: ["marketing"],
        volume: "low"
      }
    )

  IO.puts(campaign["status"]) # "in_review"
  ```

  ```bash cURL theme={null}
  curl -X PATCH https://api.surge.app/campaigns/{campaign_id} \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "consent_flow": "Users enter their phone number during checkout. An unchecked checkbox labeled \"I agree to receive text messages from Acme Corp\" is present. Required disclosures are shown: \"Message frequency varies. Msg&data rates apply. Reply STOP to opt out. View our Privacy Policy at acme.com/privacy.\"",
      "message_samples": [
        "Acme Corp: Your order #12345 has shipped! Track it: https://acme.com/track",
        "Acme Corp: Your cart is waiting. Complete your order: https://acme.com/cart. Reply STOP to opt out."
      ],
      "description": "Marketing messages to opted-in customers",
      "privacy_policy_url": "https://acme.com/privacy",
      "terms_and_conditions_url": "https://acme.com/terms",
      "use_cases": ["marketing"],
      "volume": "low"
    }'
  ```
</CodeGroup>

After updating a `changes_needed`, `rejected`, or `canceled` campaign, the campaign moves back to `pending` and then `in_review`.

## Checking campaign status

Poll the campaign endpoint to track the current status:

<CodeGroup>
  ```python Python theme={null}
  from surge import Surge

  surge = Surge()
  campaign = surge.campaigns.retrieve("{campaign_id}")
  print(campaign.status)
  ```

  ```typescript TypeScript theme={null}
  import Surge from "@surgeapi/node";

  const surge = new Surge();
  const campaign = await surge.campaigns.retrieve("{campaign_id}");
  console.log(campaign.status);
  ```

  ```ruby Ruby theme={null}
  require "surge_api"

  surge = SurgeAPI::Client.new
  campaign = surge.campaigns.retrieve("{campaign_id}")
  puts campaign.status
  ```

  ```elixir Elixir theme={null}
  # Surge.Campaigns.get isn't typed in the Elixir SDK yet — use the raw client.
  client = Surge.Client.new(System.get_env("SURGE_API_KEY"))
  {:ok, campaign} = Surge.Client.request(client, :get, "/campaigns/{campaign_id}")
  IO.puts(campaign["status"])
  ```

  ```bash cURL theme={null}
  curl https://api.surge.app/campaigns/{campaign_id} \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</CodeGroup>

```json theme={null}
{
  "id": "cpn_01jrzhe8d9enptypyx360pcmxl",
  "status": "in_review",
  "use_cases": ["marketing"],
  "volume": "low"
}
```

Or subscribe to the `campaign.approved` webhook event to get notified the moment a campaign goes `active`. This avoids polling. See [Webhook Events](../receiving/events).

## Rejected campaigns

<Warning>
  A `rejected` status is more serious than `changes_needed`. Unlike `changes_needed`, a rejection doesn't automatically move the campaign back into the queue when you update it — you may need to create a new campaign entirely. Rejections related to spam or misuse may also affect your account's ability to register future campaigns.
</Warning>

If your campaign is rejected:

<Steps>
  <Step title="Read the rejection notice carefully">
    Reviewers include the specific reason. Don't guess.
  </Step>

  <Step title="Address every listed issue before doing anything else">
    Partial fixes typically lead to another rejection.
  </Step>

  <Step title="Contact Surge support">
    Confirm whether to resubmit the existing campaign or start a new one — the right move depends on the reason.
  </Step>
</Steps>

Make sure your opt-in flow, sample messages, and business identity are solid before resubmitting. If you're unsure what went wrong, [Avoiding Rejection](./avoiding-rejection) covers the seven most common patterns.
