# Fixing a Rejected or Changes-Needed Campaign
URL: /docs/registration/fixing-rejected
LLM index: /llms.txt
Description: How to read reviewer feedback, update a campaign in a valid status, and get back to production after rejection.

# Fixing a Rejected or Changes-Needed Campaign

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
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 submitted the campaign, Surge has received it
- **`pending`**: queued for submission to the carrier network
- **`in_review`**: actively being reviewed by TCR and carrier reviewers
- **`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 suspended by carrier
- **`canceled`**: you or Surge canceled the campaign

<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 items={["Python","TypeScript","Ruby","Elixir","cURL"]}>

```python Python
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
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
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
# 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
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 items={["Python","TypeScript","Ruby","Elixir","cURL"]}>

```python Python
from surge import Surge

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

```typescript TypeScript
import Surge from "@surgeapi/node";

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

```ruby Ruby
require "surge_api"

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

```elixir Elixir
# 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
curl https://api.surge.app/campaigns/{campaign_id} \
  -H "Authorization: Bearer YOUR_API_KEY"
```

</CodeGroup>

```json
{
  "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.
