Register via API
This walkthrough takes you from a fresh Surge platform 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.
Prerequisites
- A Surge platform and API key
- Your business EIN (US) or CBN (Canada)
- A published privacy policy and terms of service URL
Step 1: Create an account
An account represents a brand in carrier terms. Create one with your business details:
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...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...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"
}
}
}'The response includes your new account ID. Save it; all subsequent requests use it.
{
"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 for the reasoning.
Step 2 (optional): Check what's missing
The status endpoint tells you whether your account is ready to register a campaign and what fields are still needed:
status = surge.accounts.retrieve_status(
"acct_01jrzhe8d9enptypyx360pcmxj",
capabilities=["local_messaging"],
)
print(status.capabilities["local_messaging"].status)
print(status.capabilities["local_messaging"].fields_needed)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);curl "https://api.surge.app/accounts/ACCOUNT_ID/status?capabilities=local_messaging" \
-H "Authorization: Bearer YOUR_API_KEY"The response includes a fields_needed array listing any missing or invalid fields:
{
"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.
Step 3 (optional): 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:
surge.accounts.update(
"acct_01jrzhe8d9enptypyx360pcmxj",
organization={
"contact": {"phone_number": "+18015551234"},
"address": {"line1": "123 Main St"},
},
)await surge.accounts.update("acct_01jrzhe8d9enptypyx360pcmxj", {
organization: {
contact: { phone_number: "+18015551234" },
address: { line1: "123 Main St" },
},
});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"
}
}
}'Repeat steps 2 and 3 until capabilities.local_messaging.status is "ready".
Step 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.
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"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"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"
}'The campaign goes straight to in_review:
{
"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 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 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 webhook fires per number once the attachment completes. Wait for that event before sending real traffic from a given number.
Step 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 webhook for each number once it's ready.
phone_number = surge.phone_numbers.purchase(
"acct_01jrzhe8d9enptypyx360pcmxj",
type="local",
area_code="801",
)
print(phone_number.number) # +18015559876const phoneNumber = await surge.phoneNumbers.purchase(
"acct_01jrzhe8d9enptypyx360pcmxj",
{
type: "local",
area_code: "801",
},
);
console.log(phoneNumber.number); // +18015559876curl -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"
}'{
"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 for every valid value of
type,industry,use_cases, and other enum fields - Learn what trips up most registrations in Avoiding Rejection
- Start sending messages with your new number