> ## 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.

# Register via API

> Step-by-step: create an account, submit a campaign, purchase a phone number, and reach production via the Surge API.

This walkthrough takes you from a fresh Surge project to a fully registered campaign with a dedicated phone number. If you submit all required fields in step 1, you can skip steps 2 and 3 entirely.

<Note>
  **Sole proprietor without an EIN?** This walkthrough assumes you have a tax ID (EIN or CBN). If you don't, follow the [Sole Proprietor Path](./sole-proprietor) — different fields, no EIN required, and dashboard-first.
</Note>

## Prerequisites

* **Surge project and API key**: Create the project at [hq.surge.app](https://hq.surge.app) and copy a key from **API Keys**.
* **Business tax ID**: Your EIN (US) or CBN (Canada).
* **Privacy policy and terms of service URLs**: Both must be live and publicly indexable when reviewers visit them.

## 1. Create an account

An account represents a brand in carrier terms. Create one with your business details:

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

  surge = Surge()

  account = surge.accounts.create(
      name="Acme Corp",
      organization={
          "type": "private_corporation",
          "registered_name": "Acme Corp Inc.",
          "identifier_type": "ein",
          "identifier": "12-3456789",
          "website": "https://acme.com",
          "industry": "technology",
          "regions_of_operation": ["usa_and_canada"],
          "email": "hello@acme.com",
          "address": {
              "line1": "123 Main St",
              "locality": "Salt Lake City",
              "region": "UT",
              "postal_code": "84101",
              "country": "US",
          },
          "contact": {
              "first_name": "Jane",
              "last_name": "Smith",
              "email": "jane@acme.com",
              "phone_number": "+18015551234",
              "title": "ceo",
          },
      },
  )

  print(account.id)  # acct_01j...
  ```

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

  const surge = new Surge();

  const account = await surge.accounts.create({
    name: "Acme Corp",
    organization: {
      type: "private_corporation",
      registered_name: "Acme Corp Inc.",
      identifier_type: "ein",
      identifier: "12-3456789",
      website: "https://acme.com",
      industry: "technology",
      regions_of_operation: ["usa_and_canada"],
      email: "hello@acme.com",
      address: {
        line1: "123 Main St",
        locality: "Salt Lake City",
        region: "UT",
        postal_code: "84101",
        country: "US",
      },
      contact: {
        first_name: "Jane",
        last_name: "Smith",
        email: "jane@acme.com",
        phone_number: "+18015551234",
        title: "ceo",
      },
    },
  });

  console.log(account.id); // acct_01j...
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.surge.app/accounts \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Acme Corp",
      "organization": {
        "type": "private_corporation",
        "registered_name": "Acme Corp Inc.",
        "identifier_type": "ein",
        "identifier": "12-3456789",
        "website": "https://acme.com",
        "industry": "technology",
        "regions_of_operation": ["usa_and_canada"],
        "email": "hello@acme.com",
        "address": {
          "line1": "123 Main St",
          "locality": "Salt Lake City",
          "region": "UT",
          "postal_code": "84101",
          "country": "US"
        },
        "contact": {
          "first_name": "Jane",
          "last_name": "Smith",
          "email": "jane@acme.com",
          "phone_number": "+18015551234",
          "title": "ceo"
        }
      }
    }'
  ```
</CodeGroup>

The response includes your new account ID. Save it; all subsequent requests use it.

```json theme={null}
{
  "id": "acct_01jrzhe8d9enptypyx360pcmxj",
  "name": "Acme Corp",
  "brand_name": null,
  "time_zone": null,
  "organization": {
    "type": "private_corporation",
    "registered_name": "Acme Corp Inc.",
    "identifier_type": "ein",
    "identifier": "12-3456789",
    "website": "https://acme.com",
    "industry": "technology",
    "regions_of_operation": ["usa_and_canada"],
    "email": "hello@acme.com",
    "address": {
      "line1": "123 Main St",
      "locality": "Salt Lake City",
      "region": "UT",
      "postal_code": "84101",
      "country": "US"
    },
    "contact": {
      "first_name": "Jane",
      "last_name": "Smith",
      "email": "jane@acme.com",
      "phone_number": "+18015551234",
      "title": "ceo"
    }
  }
}
```

If you're running this for each of your own customers, create one account per customer. See [One Account per Customer](./one-account-per-customer) for the reasoning.

## Optional: 2. Check what's missing

The status endpoint tells you whether your account is ready to register a campaign and what fields are still needed:

<CodeGroup>
  ```python Python theme={null}
  status = surge.accounts.retrieve_status(
      "acct_01jrzhe8d9enptypyx360pcmxj",
      capabilities=["local_messaging"],
  )

  print(status.capabilities["local_messaging"].status)
  print(status.capabilities["local_messaging"].fields_needed)
  ```

  ```typescript TypeScript theme={null}
  const status = await surge.accounts.retrieveStatus(
    "acct_01jrzhe8d9enptypyx360pcmxj",
    { capabilities: ["local_messaging"] },
  );

  console.log(status.capabilities.local_messaging.status);
  console.log(status.capabilities.local_messaging.fields_needed);
  ```

  ```bash cURL theme={null}
  curl "https://api.surge.app/accounts/ACCOUNT_ID/status?capabilities=local_messaging" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</CodeGroup>

The response includes a `fields_needed` array listing any missing or invalid fields:

```json theme={null}
{
  "capabilities": {
    "local_messaging": {
      "status": "incomplete",
      "fields_needed": ["organization.contact.phone_number", "organization.address.line1"],
      "errors": []
    }
  }
}
```

An empty `fields_needed` array and a `"ready"` status means you can proceed to create a campaign.

## Optional: 3. Fill the gaps

If `fields_needed` is non-empty, patch the account to add the missing information. The field names in the response are dot-delimited paths into the account object:

<CodeGroup>
  ```python Python theme={null}
  surge.accounts.update(
      "acct_01jrzhe8d9enptypyx360pcmxj",
      organization={
          "contact": {"phone_number": "+18015551234"},
          "address": {"line1": "123 Main St"},
      },
  )
  ```

  ```typescript TypeScript theme={null}
  await surge.accounts.update("acct_01jrzhe8d9enptypyx360pcmxj", {
    organization: {
      contact: { phone_number: "+18015551234" },
      address: { line1: "123 Main St" },
    },
  });
  ```

  ```bash cURL theme={null}
  curl -X PATCH https://api.surge.app/accounts/ACCOUNT_ID \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "organization": {
        "contact": {
          "phone_number": "+18015551234"
        },
        "address": {
          "line1": "123 Main St"
        }
      }
    }'
  ```
</CodeGroup>

Repeat steps 2 and 3 until `capabilities.local_messaging.status` is `"ready"`.

## 4. Create a campaign

A campaign tells carriers what you'll send and how users opted in. Every field here is read by human reviewers, so write for clarity.

<CodeGroup>
  ```python Python theme={null}
  campaign = surge.campaigns.create(
      "acct_01jrzhe8d9enptypyx360pcmxj",
      description="Transactional and marketing messages for Acme Corp customers who have opted in via our checkout page.",
      use_cases=["marketing"],
      includes=["links"],
      consent_flow='Users enter their phone number on our checkout page. A checkbox labeled "I agree to receive text messages from Acme Corp" is present and unchecked by default. Required disclosures appear beneath the checkbox: "Message frequency varies. Msg&data rates apply. Reply STOP to opt out."',
      message_samples=[
          "You are now opted in to messages from Acme Corp. Frequency varies. Msg&data rates apply. Reply STOP to opt out.",
          "Acme Corp: Your order #12345 has shipped. Track it here: https://acme.com/track",
          "Acme Corp: 20% off this weekend only. Shop now: https://acme.com/sale. Reply STOP to opt out.",
      ],
      volume="low",
      privacy_policy_url="https://acme.com/privacy",
      terms_and_conditions_url="https://acme.com/terms",
  )

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

  ```typescript TypeScript theme={null}
  const campaign = await surge.campaigns.create(
    "acct_01jrzhe8d9enptypyx360pcmxj",
    {
      description:
        "Transactional and marketing messages for Acme Corp customers who have opted in via our checkout page.",
      use_cases: ["marketing"],
      includes: ["links"],
      consent_flow:
        'Users enter their phone number on our checkout page. A checkbox labeled "I agree to receive text messages from Acme Corp" is present and unchecked by default. Required disclosures appear beneath the checkbox: "Message frequency varies. Msg&data rates apply. Reply STOP to opt out."',
      message_samples: [
        "You are now opted in to messages from Acme Corp. Frequency varies. Msg&data rates apply. Reply STOP to opt out.",
        "Acme Corp: Your order #12345 has shipped. Track it here: https://acme.com/track",
        "Acme Corp: 20% off this weekend only. Shop now: https://acme.com/sale. Reply STOP to opt out.",
      ],
      volume: "low",
      privacy_policy_url: "https://acme.com/privacy",
      terms_and_conditions_url: "https://acme.com/terms",
    },
  );

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

  ```bash cURL theme={null}
  curl -X POST https://api.surge.app/accounts/ACCOUNT_ID/campaigns \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "description": "Transactional and marketing messages for Acme Corp customers who have opted in via our checkout page.",
      "use_cases": ["marketing"],
      "includes": ["links"],
      "consent_flow": "Users enter their phone number on our checkout page. A checkbox labeled \"I agree to receive text messages from Acme Corp\" is present and unchecked by default. Required disclosures appear beneath the checkbox: \"Message frequency varies. Msg&data rates apply. Reply STOP to opt out.\"",
      "message_samples": [
        "You are now opted in to messages from Acme Corp. Frequency varies. Msg&data rates apply. Reply STOP to opt out.",
        "Acme Corp: Your order #12345 has shipped. Track it here: https://acme.com/track",
        "Acme Corp: 20% off this weekend only. Shop now: https://acme.com/sale. Reply STOP to opt out."
      ],
      "volume": "low",
      "privacy_policy_url": "https://acme.com/privacy",
      "terms_and_conditions_url": "https://acme.com/terms"
    }'
  ```
</CodeGroup>

The campaign goes straight to `in_review`:

```json theme={null}
{
  "id": "cpn_01jrzhe8d9enptypyx360pcmxl",
  "status": "in_review",
  "description": "Transactional and marketing messages for Acme Corp customers who have opted in via our checkout page.",
  "use_cases": ["marketing"],
  "includes": ["links"],
  "volume": "low",
  "consent_flow": "Users enter their phone number on our checkout page...",
  "message_samples": [
    "You are now opted in to messages from Acme Corp. Frequency varies. Msg&data rates apply. Reply STOP to opt out.",
    "Acme Corp: Your order #12345 has shipped. Track it here: https://acme.com/track",
    "Acme Corp: 20% off this weekend only. Shop now: https://acme.com/sale. Reply STOP to opt out."
  ],
  "privacy_policy_url": "https://acme.com/privacy",
  "terms_and_conditions_url": "https://acme.com/terms"
}
```

A few things about campaign fields:

* **`volume`**: `"low"` allows up to 2,000 SMS segments/day to T-Mobile. `"high"` allows up to 200,000/day, depending on the trust score assigned by The Campaign Registry.
* **`use_cases`**: The types of messages you'll send. Common values: `marketing`, `customer_care`, `account_notification`, `two_factor_authentication`. See the [Schema Reference](./schema-reference) for the full list.
* **`message_samples`**: 2–5 examples of what you'll actually send. The first should be a compliance opt-in confirmation. Use real brand names, not placeholders.

When the status changes to `active`, Surge fires a [`campaign.approved`](../receiving/events#campaignapproved) webhook event. The campaign is approved, but **a number isn't ready to send production messages until it's also been attached to the campaign**. Surge starts attaching numbers automatically as soon as the campaign goes active; the [`phone_number.attached_to_campaign`](../api-reference/webhooks/phone_number/attached_to_campaign) webhook fires per number once the attachment completes. Wait for that event before sending real traffic from a given number.

## 5. Purchase a phone number

You can buy a number while your campaign is in review. You just can't send production messages from it until two things have happened: the campaign is `active`, and the number has been attached to it. After the campaign is approved, attachment runs automatically; you'll receive a [`phone_number.attached_to_campaign`](../api-reference/webhooks/phone_number/attached_to_campaign) webhook for each number once it's ready.

<CodeGroup>
  ```python Python theme={null}
  phone_number = surge.phone_numbers.purchase(
      "acct_01jrzhe8d9enptypyx360pcmxj",
      type="local",
      area_code="801",
  )

  print(phone_number.number)  # +18015559876
  ```

  ```typescript TypeScript theme={null}
  const phoneNumber = await surge.phoneNumbers.purchase(
    "acct_01jrzhe8d9enptypyx360pcmxj",
    {
      type: "local",
      area_code: "801",
    },
  );

  console.log(phoneNumber.number); // +18015559876
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.surge.app/accounts/ACCOUNT_ID/phone_numbers \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "local",
      "area_code": "801"
    }'
  ```
</CodeGroup>

```json theme={null}
{
  "id": "pn_01jrzhe8d9enptypyx360pcmxm",
  "number": "+18015559876",
  "type": "local",
  "campaign_id": null
}
```

`campaign_id` is `null` because the campaign isn't active yet. Surge attaches phone numbers to campaigns automatically. Two things trigger attachment:

* **Purchase while a campaign is active**: Surge attaches the number immediately.
* **Campaign becomes active after purchase**: Surge attaches all existing unattached numbers on the account when the campaign is approved.

When attachment completes, Surge fires a `phone_number.attached_to_campaign` webhook event. Once you receive that event, the number can send production traffic.

## What's next

* Review the [Schema Reference](./schema-reference) for every valid value of `type`, `industry`, `use_cases`, and other enum fields
* Learn what trips up most registrations in [Avoiding Rejection](./avoiding-rejection)
* Start [sending messages](../sending/send-one) with your new number
