Compliance & registration

Carriers only deliver business texting at volume from a registered brand (your business) and campaign (what you send, and how people opted in). Most registrations are rejected for the same few reasons: a privacy policy without the SMS clause, an opt-in form missing "Msg & data rates may apply", a pre-checked consent box. SimpleSMS checks for all of them before anything is filed, writes the copy you are missing, and files the registration for you.

Registration is free. It works with test keys exactly as it does with live keys.

How it works

  1. You create a registration: business name, website, the page where people opt in, what you will send (the use case), and 2 to 5 real examples of those texts. In the console you only type your website: we fill in the rest for you to review.
  2. We check your website and your examples right away (details below). Every finding comes with the evidence (the URL and the text we found, or what we looked for) and, if it fails, the exact text to paste.
  3. You fix and recheck until every check passes and the examples are yours. The registration is then ready.
  4. You submit. SimpleSMS's compliance team files the brand and campaign with the carriers, usually within 1 business day (submitted, then in_review).
  5. Carriers decide, usually in 3 to 7 business days: approved, or rejected with a plain-language reason and the exact fix. Fix it, recheck, and submit again.

Every registration answers five questions at all times, in its guidance field and on the console Registration page: what happened, why, who acts next (you, delivered, carriers, or nobody), exactly what to do, and what happens after.

Fill in from your website

Give us the website and we suggest the rest of the registration: business name, a legal name if the site states one, the use case, a description, the opt-in, privacy and terms pages, support email, contact phone, address and industry. The console form does this when you click Fill in from my website; over the API it is one call:

bash
curl -X POST https://api.joinsimplesms.com/v1/registrations/prefill \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"website": "https://acmeplumbing.com"}'
json
{
  "object": "registration_prefill",
  "reachable": true,
  "error": null,
  "fields": {
    "website": "https://acmeplumbing.com/",
    "business_name": "Acme Plumbing",
    "use_case": "account_notification",
    "description": "Licensed plumbers serving Springfield since 1998. Acme Plumbing sends account alerts and notifications to customers who have opted in. ...",
    "opt_in_url": "https://acmeplumbing.com/text-updates",
    "privacy_url": "https://acmeplumbing.com/privacy",
    "terms_url": "https://acmeplumbing.com/terms",
    "support_email": "help@acmeplumbing.com",
    "brand": { "legal_name": "Acme Plumbing LLC", "vertical": "CONSTRUCTION", "contact_email": "help@acmeplumbing.com", "contact_phone": "+14155550132" }
  },
  "suggested": ["business_name", "use_case", "description", "opt_in_url", "privacy_url", "terms_url", "support_email", "brand.legal_name", "brand.vertical", "brand.contact_email", "brand.contact_phone"],
  "sources": { "business_name": "page", "opt_in_url": "page" },
  "candidates": {
    "opt_in": [{ "url": "https://acmeplumbing.com/text-updates", "confidence": 1, "reason": "Has a phone number field and SMS consent wording" }],
    "privacy": [{ "url": "https://acmeplumbing.com/privacy", "confidence": 1, "reason": "Link labelled \"Privacy Policy\"" }],
    "terms": [{ "url": "https://acmeplumbing.com/terms", "confidence": 1, "reason": "Link labelled \"Terms of Service\"" }]
  },
  "about": "Licensed plumbers serving Springfield since 1998.",
  "ai": false
}
  • fields is shaped like the body of POST /v1/registrations. Review it, add what only you know, and send it on. We never suggest your EIN or entity type.
  • suggested names every field we filled in, and sources says where each came from: page (read from your pages by fixed rules) or ai.
  • candidates lists the pages that could be your opt-in, privacy policy and terms, best first, each with the reason. fields holds the best one when we are confident enough; pick another from the list if we chose wrong.
  • A site we cannot read returns reachable: false with the reason in error, and no suggestions.

What we read, and AI. We fetch your homepage and the few pages it links to that look like your sign-up, contact, privacy policy and terms pages: public pages only, under the same rules as the website check below. Names, contact details, address and page links are read with fixed rules. Where AI drafting is available (ai: true), a third-party AI model also reads the text of those public pages to draft the description, pick the use case and industry, and write three example messages (fields.sample_messages). It sees nothing from your account, is not trained on what it reads (Privacy §4), and can only choose a business name the page states and an opt-in page from candidates. If its examples would not pass the example check they are left out.

Suggestions are drafts. Nothing is saved or filed by this call. Example messages we draft are starter drafts: send them as starter_messages when you create the registration and it keeps samples_source: "starter" until you edit them (send sample_messages) or confirm them (samples_confirmed: true). Suggestions can be wrong; you are responsible for what you submit.

One read per website per account every 10 minutes: asking again sooner returns the same suggestions. Limit: 10 websites a minute, separate from the website check's limit.

Use cases

use_caseRegistry use caseFor
2fa2FAOne-time passcodes
account_notificationACCOUNT_NOTIFICATIONAccount alerts and notices
customer_careCUSTOMER_CARESupport conversations
delivery_notificationDELIVERY_NOTIFICATIONOrder and delivery status
fraud_alertFRAUD_ALERTSuspicious-activity alerts
security_alertSECURITY_ALERTAccount security notices
higher_educationHIGHER_EDUCATIONCampus and enrollment updates
marketingMARKETINGOffers and promotions
mixedMIXEDService messages and marketing
polling_votingPOLLING_VOTINGSurveys (not political)
public_service_announcementPUBLIC_SERVICE_ANNOUNCEMENTCommunity alerts
low_volumeLOW_VOLUMESmall mixed programs

Pick the one that matches what you actually send. Sending a different kind of message than you registered is a policy violation (Messaging Policy §6); register another campaign instead.

What the website check looks for

FindingPasses when
site_reachableYour website loads publicly over http(s).
business_identityThe business name appears in the page text (legal suffixes like LLC are ignored).
privacy_policy_existsA privacy policy loads (we follow the "Privacy" link if you do not give a URL).
privacy_no_sharingIt says mobile / SMS opt-in data is not shared with third parties.
terms_existTerms of service load (we follow the "Terms" link).
terms_sms_programThe terms describe the SMS program, with STOP and HELP.
optin_reachableThe opt-in page loads.
optin_phone_fieldIt has a phone number field, or a "Text KEYWORD to NUMBER" instruction.
optin_brandThe disclosure names your brand.
optin_message_typesIt says what messages to expect.
optin_frequency"Message frequency varies", or a cadence like "4 msgs per week".
optin_rates"Msg & data rates may apply".
optin_stop"Reply STOP to cancel".
optin_help"Reply HELP for help".
optin_linksLinks to the privacy policy and terms.
optin_not_precheckedThe consent checkbox starts unchecked.

The check is deterministic pattern matching; no AI is involved. (The only place AI may appear in registration is the optional drafting in Fill in from your website.) The generated copy is written to pass it: paste the fix, recheck, and that finding passes. Without an opt-in URL we check your homepage, and a missing phone field there is a warning rather than a failure.

We fetch only the public pages you name (and the policy links on your homepage), from our servers, with a short timeout and a size cap. Addresses that are not on the public internet are never fetched. We keep the findings and their short evidence snippets, not the pages.

Your example messages

Carriers compare the examples on your registration with the texts you actually send. Examples that do not match your traffic are one of the most common reasons a registration is rejected, and a sender suspended later. So the examples we file are yours, not ours.

Send them as sample_messages: 2 to 5 strings, 20 to 320 characters each, written as a recipient would get them (a real-looking name, order number or code, not a template variable). description (optional, 40 to 500 characters) says what you send and to whom; leave it out and we file a generated one.

If you create a registration without sample_messages, it comes back with starter drafts for your use case and samples_source: "starter": our templates, or the drafts written for your business if you sent starter_messages from Fill in from your website (drafts that would not pass the checks below are ignored). Starter drafts are never filed: edit them, or send samples_confirmed: true to /recheck if they already read like your texts. Either makes samples_source "customer".

sample_check holds the findings, in the same shape as the website check:

FindingPasses when
samples_confirmedThe examples are yours (edited or confirmed), not starter drafts.
samples_countThere are 2 to 5.
samples_lengthEach is 20 to 320 characters.
samples_brandEvery example names your business (the name without LLC / Inc, its leading word, or its initials).
samples_opt_outAt least one says how to stop ("Reply STOP to opt out"). Required for marketing and mixed; a warning for other use cases; not asked of 2fa.
samples_linksNo public link shorteners.
samples_placeholdersNo unfilled variables like {{name}} or [Brand].
samples_distinctNo two examples are the same.
samples_use_case2fa only: an example shows a code.
samples_descriptionYour own description, if you wrote one, is 40 to 500 characters.

These are deterministic rules; no AI is involved in the check. They cannot tell whether an example matches what you will send: that part is on you, and it is what carriers judge. A registration is ready only when the website check passes, the example check passes, and samples_source is "customer". A recheck that changes only the examples does not fetch your website again.

What we file, and the copy we keep

When you submit, we store an exact copy of what was filed: your business details, example messages, description, the opt-in story, and how the checks stood. A resubmission adds a new copy; a copy is never changed. Read them at GET /v1/registrations/{id}/submissions or under Filed versions on the console Registration page, so you can always show what the carriers reviewed.

Generated copy

Every registration includes generated, built from your business name and use case:

  • opt_in_cta: the disclosure to place next to the phone field
  • privacy_clause and sms_terms: paste into your privacy policy and terms
  • sample_messages: the starter drafts (what is filed is the top-level sample_messages, repeated in campaign.sample_messages)
  • help_reply, stop_reply, opt_in_confirmation
  • brand and campaign: the full registration draft, including message_flow, keywords, and the embedded-link, embedded-phone, age-gated and direct-lending flags. brand.missing lists what the registry still needs from you (legal name, EIN, address, ...); send it in brand.

API

bash
# Create (runs the website check immediately)
curl -X POST https://api.joinsimplesms.com/v1/registrations \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "business_name": "Acme Plumbing LLC",
    "website": "https://acmeplumbing.com",
    "opt_in_url": "https://acmeplumbing.com/text-updates",
    "use_case": "customer_care",
    "support_email": "help@acmeplumbing.com",
    "sample_messages": [
      "Acme Plumbing: Hi Sam, your plumber Dana arrives tomorrow between 9 and 11 AM. Reply STOP to opt out.",
      "Acme Plumbing: Your job #4821 is done. Reply here with any questions about the repair."
    ],
    "brand": { "legal_name": "Acme Plumbing LLC", "ein": "12-3456789" }
  }'

# Fix the site, then recheck (any field can be corrected in the same call)
curl -X POST https://api.joinsimplesms.com/v1/registrations/reg_.../recheck \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"opt_in_url": "https://acmeplumbing.com/signup"}'

# Submit once status is "ready"
curl -X POST https://api.joinsimplesms.com/v1/registrations/reg_.../submit \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY"

# Get one / its filed copies / list all
curl https://api.joinsimplesms.com/v1/registrations/reg_... \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY"
curl https://api.joinsimplesms.com/v1/registrations/reg_.../submissions \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY"
curl https://api.joinsimplesms.com/v1/registrations \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY"

A response, trimmed:

json
{
  "id": "reg_a1B2c3D4e5F6",
  "object": "registration",
  "status": "checks_failed",
  "guidance": {
    "what_happened": "The website check found 1 problem: consent box is not pre-checked.",
    "why": "Carrier reviewers reject registrations whose website or opt-in is missing these disclosures. Fixing them now avoids a rejection later.",
    "next_actor": "you",
    "next_step": "Fix each failed item using its copy-paste text, publish the change on your website, then click Recheck website. If a URL was wrong, correct it when you recheck.",
    "after": "When every check passes the registration becomes Ready to submit."
  },
  "check": {
    "passed": 15, "failed": 1,
    "findings": [{
      "id": "optin_not_prechecked",
      "status": "fail",
      "evidence": { "url": "https://acmeplumbing.com/text-updates", "snippet": "<input type=\"checkbox\" name=\"sms_consent\" checked>" },
      "why": "Consent must be an action the person takes...",
      "fix": { "summary": "Remove the `checked` attribute from the consent checkbox...", "copy": null }
    }]
  },
  "rejection": null,
  "generated": { "opt_in_cta": "By checking this box, I agree to receive ..." }
}

States: draft → checks_failed / ready → submitted → in_review → approved / rejected. Recheck works in draft, checks_failed, ready and rejected; submit only in ready. Anything else returns 409 invalid_state with a message saying what to do instead. Creating and rechecking share a budget of 20 website checks per minute per account.

Rejections

A rejection carries rejection.code, a plain explanation, the exact fix, any note from our compliance team, and related_findings; those findings are marked flagged_by_rejection on the next recheck. Codes: website_unreachable, brand_mismatch, privacy_sharing_clause, terms_missing_sms, optin_disclosure_incomplete, optin_not_verifiable, prechecked_consent, samples_missing_brand, samples_use_case_mismatch, message_flow_unclear, url_shortener_or_link_mismatch, restricted_content, other.

Events

registration.updated fires on every status change (pollable at /v1/events, pushed to webhooks). data has id, status, previous_status, change (e.g. resubmitted), and reason_code on rejections.

Sending before approval

A live number is test only until its registration is approved: it can text your verified numbers (your own phone, and any number you verify under Billing → Verified numbers), so you can build and test with real texts while the carriers review. It cannot text anyone else yet. Carriers block traffic from unregistered numbers, so we stop it before it leaves rather than let it fail downstream.

A send to an unverified recipient from a number that is not yet active answers:

HTTP/1.1 403 X-SimpleSMS-Sender-State: test_only | pending | action_needed { "error": { "code": "sender_not_registered", "param": "from", "message": "+14155550132 is still being registered with the carriers, so it cannot text this recipient yet. ..." } }

Nothing is sent and nothing is charged. Once the registration is approved, every live number on the account is linked to it automatically and becomes active; watch sender.state on the number or subscribe to number.sender_updated. Test keys and sandbox numbers are never affected.

Going live

Submitting a registration from a sandbox account is also your request for live access. There is no second form: we read the business, website, use case and opt-in from the registration. You can keep building in the sandbox while both are reviewed.

A registration must be complete before it can be submitted. If business details are missing (legal name, tax ID, address, industry, contact email), submit answers 400 naming them, and the registration stays ready.

Carrier review usually takes 3 to 7 business days. If it runs past 10 days we flag the registration (review_stalled: true), our team follows up with the carriers, and your numbers show action_needed with an explanation, so a review never sits silently.

Opt-in proof

Messaging Policy §1.4 asks you to keep evidence of every consent. Record it with the opt-in and it lives in your consent ledger, next to the opt-in it proves:

bash
curl -X POST https://api.joinsimplesms.com/v1/consent/+14155550132 \
  -H "Authorization: Bearer $SIMPLESMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "opted_in",
    "proof": {
      "source": "web_form",
      "collected_at": "2026-09-30T18:04:05Z",
      "page_url": "https://acmeplumbing.com/text-updates",
      "disclosure": "By checking this box, I agree to receive ...",
      "ip": "203.0.113.7",
      "user_agent": "Mozilla/5.0 ...",
      "registration_id": "reg_a1B2c3D4e5F6"
    }
  }'
Field
sourceRequired. web_form, keyword, paper, checkout, or verbal.
collected_atWhen they consented (ISO or epoch ms). Defaults to now.
page_urlRequired for web_form.
disclosureThe exact wording they saw (up to 2,000 characters).
ip, user_agentFor online forms.
registration_id, campaign_idThe registration or carrier campaign it was collected under.

GET /v1/consent/{phone} returns proof on the history entry, and GET /v1/consent/export?type=ledger exports every consent event with proof_* columns. Keep proof for at least four years after your last message to the number.

This page is educational, not legal advice.