saperly
Guides

Compliance

Consent, disclosures, and 10DLC are first-class in Saperly and enforced independently of any LLM — outbound contact without recorded consent is blocked before it goes out.

Saperly bakes TCPA compliance in. Consent, disclosures, and 10DLC registration are first-class objects enforced independently of any LLM — so a misbehaving model (or a model you swap out) can never route around them. Outbound voice or SMS to a peer without recorded consent is rejected before it goes out.

Base URL https://api.saperly.com. Authenticate with Authorization: Bearer sk_live_...; the workspace is resolved from the token.

Consent is recorded per (number, peer) pair: a ConsentRecord says this Saperly number has consent to contact that peer number. Saperly checks it automatically on every outbound message and call.

ConsentTypeMeaning
implied_inboundThe peer contacted you first (an inbound call or SMS), which implies consent to reply.
explicit_outboundThe peer explicitly opted in to be contacted — required before you initiate outbound contact.

Endpoints

MethodPathReturnsScope
GET/consentConsentRecord[]
POST/consentConsentRecord (201)consent:write
POST/consent/revokeConsentRecordconsent:write
GET/consent/check?numberId=&peerNumber={ hasConsent, type? }

The ConsentRecord shape

FieldTypeNotes
idstringRecord id.
numberIdstringThe Saperly number.
peerNumberstringThe peer, in E.164 (e.g. +15555550123).
consentTypeConsentTypeimplied_inbound or explicit_outbound.
sourcestringFree-form provenance label, e.g. "sms_optin" or "call_recording_disclosure".
grantedAtstringISO 8601 timestamp.
revokedAtstring | nullISO 8601 timestamp once revoked, else null.

Worked flow: check, record, then contact

Always check consent before initiating outbound contact, record explicit_outbound consent when the peer opts in, then send. The source label is your audit trail — make it describe how consent was obtained.

# 1. Check before contacting
curl "https://api.saperly.com/consent/check?numberId=num_...&peerNumber=%2B15555550123" \
  -H "Authorization: Bearer $SAPERLY_API_KEY"
# -> { "hasConsent": false }

# 2. The peer opts in — record explicit outbound consent
curl -X POST https://api.saperly.com/consent \
  -H "Authorization: Bearer $SAPERLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "numberId": "num_...",
    "peerNumber": "+15555550123",
    "consentType": "explicit_outbound",
    "source": "sms_optin"
  }'

# 3. Now the send/call is allowed (see Messaging / Voice)
import { Saperly } from '@saperly/sdk'

const saperly = new Saperly({ apiKey: process.env.SAPERLY_API_KEY! })

// 1. Check
const { hasConsent } = await saperly.consent.check({
  numberId: 'num_...',
  peerNumber: '+15555550123',
})

// 2. Record on opt-in
if (!hasConsent) {
  await saperly.consent.record({
    numberId: 'num_...',
    peerNumber: '+15555550123',
    consentType: 'explicit_outbound',
    source: 'sms_optin',
  })
}

// 3. Now send — see Messaging
await saperly.messages.send({
  fromNumberId: 'num_...',
  to: '+15555550123',
  body: 'Thanks for opting in!',
})

Outbound without consent is a TCPA violation

Initiating an outbound call or SMS to a peer with no recorded consent is a TCPA violation — Saperly blocks it for you and the request fails with consent_required (403). Record explicit_outbound consent first, or rely on implied_inbound consent created when the peer contacts you.

POST /consent/revoke with { numberId, peerNumber } sets revokedAt and stops further outbound contact. Two things revoke consent without an explicit call:

  • STOP — an inbound SMS with the STOP keyword automatically revokes the sender's consent. Subsequent outbound sends to that number are blocked.
  • HELP — an inbound HELP keyword is logged (it does not change consent).
curl -X POST https://api.saperly.com/consent/revoke \
  -H "Authorization: Bearer $SAPERLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "numberId": "num_...", "peerNumber": "+15555550123" }'

Disclosures

A disclosure is the AI notice spoken at the start of a call so the caller is told they are talking to an AI. It lives on the connection, not as a separate resource: each connection has complianceEnabled (a boolean, on by default) and a disclosure string. When compliance is on, the disclosure is spoken as the line's first, uninterruptible utterance — the TCPA notice — before the agent takes a turn, across every mode (hosted, manual, and OpenAI-realtime).

// The disclosure is a field on the connection — set it when you create or update one
await saperly.connections.create({
  name: 'support line',
  instructions: 'You are a friendly support agent for Acme Inc.',
  complianceEnabled: true,
  disclosure: 'You are speaking with an AI assistant for Acme.',
})

Leave disclosure empty with complianceEnabled on and Saperly fills a standard, org-named default so the line is never silently non-disclosing. Set complianceEnabled to false and there is no forced opener. When a recorded disclosure is the basis for contact, record it as a consent source (e.g. "call_recording_disclosure") so the provenance is captured in your audit trail.

10DLC

10DLC is the US carrier registration that certifies an application-to-person (A2P) SMS campaign. Registering a brand and campaign is what lets your US SMS deliver reliably instead of being filtered. Saperly tracks the registration and advances its status as carrier webhooks arrive.

Campaign status

TenDlcStatusMeaning
pendingSubmitted to the carrier, awaiting review.
approvedCertified — A2P SMS is cleared.
rejectedThe carrier declined the campaign.
suspendedA previously approved campaign was suspended.

The TenDlcCampaign shape

FieldTypeNotes
idstringCampaign id.
useCasestringThe declared A2P use case (e.g. 2fa, customer_care).
descriptionstringHuman-readable campaign description.
statusTenDlcStatuspending on creation, advanced by webhook.
createdAtstringISO 8601 timestamp.

Registering a campaign

Register with { useCase, description }. The campaign starts pending; the final status arrives by webhook — listen for 10dlc status events rather than polling.

{
  "useCase": "customer_care",
  "description": "Appointment reminders and support replies for Acme Clinic."
}

10DLC is primarily a dashboard + webhook flow today

10DLC management currently lives mostly in the dashboard, and campaign status updates arrive by webhook as the carrier reviews the brand and campaign. Wire up a webhook for 10dlc events so your system learns when a campaign moves to approved (or rejected / suspended) without polling.

  • Messaging — SMS sends; consent is enforced on every outbound message.
  • Voice — outbound calls; consent is enforced before the call starts.
  • Webhooks — receive STOP/HELP, delivery receipts, and 10DLC status events.
  • Core concepts — where compliance sits in the Saperly model.
  • API reference — every consent endpoint with a try-it playground.

On this page