Compliance
Every outbound call Sphoro Voice places is checked before the phone rings — against your do-not-call list, against consent where your account requires it, and against the clock where the person you are calling actually is. This page is what those rules are, what each refusal means, and which parts stay yours.
The checks, in the order they run
The order is not an optimisation. Each one is asked only if the one before it said nothing, and a call has to clear all of them twice — see below.
| # | Check | What it asks |
|---|---|---|
| 1 | The number | Is this a dialable number at all? Formatting is stripped and forgiven; what is left has to be a plausible international number. |
| 2 | Suppression | Is it on your do-not-call list? Asked first of the three real checks because nothing overrides it — not consent, not the hour, not a different campaign. |
| 3 | Consent | If your account requires one, is there a valid record of who agreed to be called, when, and how? Asked before the clock so that a number which may never be called is reported as such, rather than as “try again at 8am”. |
| 4 | The calling window | Is it a reasonable hour where they are? Not where you are, and not where this server is. |
Checked twice, and the second one is the one that counts
Once when you create the call, so you find out immediately — that is the
422 you get back — and again in the instant before the number is dialled.
The second check is the one with legal weight. A call created at 8:55pm can reach the front
of the queue at 9:05pm, and somebody can opt out in the minutes between; only the check next to
the dial can see either. If that check refuses, the call ends as failed with the
verdict on end_reason — except for the calling window, which is re-tried rather
than abandoned.
The calling window
Stated in the local time of the person you are calling. Not your local time, and not this server's — which is the whole reason the platform has to work out where a number is before it can decide anything.
| On this deployment | Setting |
|---|---|
| Calling hours | 08:00 to 21:00, local to the number. The end is exclusive: at 21:00 the window has closed, so the last
call starts a minute before it. |
| Days | Every day. |
| Consent record | Not required. A number with no consent record on file is still callable, provided it clears the other checks. |
These are set by whoever operates this deployment. Your account can set two of them for
its own calls — whether a consent record is required, and whether the calling window is
honoured at all — under Workspace → Compliance in the portal, or through
PUT /v1/calls/compliance/policy. The do-not-call list is deliberately not one of
them: nothing an account can set takes a number off it. The pre-flight check below always
answers with the rules in force for you, after any of that has been applied, which
is why it is worth asking rather than reading this table.
How we work out what time it is where a number is
| Number | How the zone is found |
|---|---|
North America (+1) | By area code. Codes that genuinely straddle two zones are deliberately absent from the table, and absent means refused. |
| Single-zone countries | By country calling code. India, the UK, Singapore, the UAE and about thirty others. |
| Countries spanning several zones | Refused. Russia, Brazil, Australia and Mexico are deliberately not listed, because one guess for them is wrong for most of the country. |
Zones are named rather than stored as fixed offsets, so daylight saving is handled properly — Arizona and Saskatchewan do not observe it, and an offset table gets them wrong for two-thirds of the year without ever looking broken.
unknown_timezone rather than a call at six in the morning. If a country
you dial is refused this way, say so — the answer is a table entry, not a workaround at your
end.Every verdict, and what to do about it
These strings are stable. They come back from the pre-flight check below, they are written
to end_reason on a call that was refused, and they are what an audit is
reconstructed from — so they are safe to branch on in your own code.
| Verdict | What it means, and what to do |
|---|---|
allowed | Nothing stood in the way. The call is placed, or queued for the dialer.Nothing to do. |
invalid_numberfinal | The number is not a dialable international number — too short, too long, or not a number at all once the formatting is stripped.Fix it at the source. Formatting is forgiven; missing digits are not. |
suppressedfinal | The number is on your do-not-call list. Nothing overrides this — not consent on file, not a different agent, not a different campaign.If it is there in error, remove it deliberately with DELETE /v1/calls/suppressions/{number}. |
no_consentfinal | Your account requires a consent record before a number may be called, and this number has none.Record how consent was obtained before dialling. See below. |
consent_expiredfinal | A consent record exists, and its expires_at has passed.Obtain permission again and record it afresh. The lapsed record is kept. |
consent_revokedfinal | The person withdrew permission. Revoking also adds the number to the do-not-call list, so this verdict is usually followed by suppressed.Do not call. Re-recording consent does not undo a revocation. |
unknown_timezonefinal | We could not work out what time it is where the number is, so the calling window cannot be honoured.Nothing to retry — this is a property of the number, not of the moment. See the window section for which numbers resolve. |
outside_calling_windowwait and it changes | It is too early or too late where the number is, or the day is one your policy excludes.Wait. This is the one verdict that changes on its own: the response carries callable_at, and a queued call is re-tried rather than dropped. |
compliance_lookup_failedfinal | A check could not be completed — the do-not-call list was unreadable, or consent is required and no consent store is configured.Report it. An unanswerable check refuses the call by design, so this is an outage, not a verdict about the number. |
Ask before you dial
One number, one answer, no call placed. Use it to screen a list before you upload it, and to answer “why did that one not go out” without opening a support ticket.
/v1/calls/compliance/checkcurl -s -G https://voice.sphoro.com/v1/calls/compliance/check \
-H "Authorization: Bearer $SPHORO_API_KEY" \
--data-urlencode "number=+12125551234"{
"number": "+12125551234",
"allowed": false,
"reason": "outside_calling_window",
"detail": "it is 22:41 for this number; calling is permitted between 08:00 and 21:00 local time",
"retryable": true,
"timezone": "America/New_York",
"callable_at": "2026-08-30T12:00:00Z"
}callable_at is absolute time, not a local one, so you can schedule against it
without doing any time-zone arithmetic yourself. It is present only when waiting would
help.
The do-not-call list
Your list, per account. It is never shared with another customer, and no other customer's list affects your calls.
/v1/calls/suppressionscurl -s -X POST https://voice.sphoro.com/v1/calls/suppressions \
-H "Authorization: Bearer $SPHORO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"number":"+12125551234","source":"registry_import","note":"national DNC, imported 2026-08-29"}'| Endpoint | What it does |
|---|---|
GET /v1/calls/suppressions | List the entries, paginated. |
POST /v1/calls/suppressions | Add one. Idempotent — the number is the key, so adding it twice replaces the entry rather than producing two that disagree about why it is there. |
DELETE /v1/calls/suppressions/{number} | Remove one. A deliberate, separate action, because taking somebody off this list is re-enabling calls to a person who asked for them to stop. |
Numbers are normalised on the way in, so the same number written four different ways in four different exports is one entry and matches all four.
Numbers on this list are not written down in full anywhere else
In logs and events a number appears masked to its last four digits. A do-not-call list is a list of people who explicitly asked to be left alone; the last four are enough to match a complaint to a call, and the rest has no business in a log aggregator.
A caller who asks to be left alone is taken off the list during the call
Not when somebody reads the transcript afterwards — by then the next campaign has already
run. When the person on the phone says any of these, their number is added to your do-not-call
list mid-call and a call.opt_out event is sent to your webhook endpoints:
stop calling do not call dont call me take me off remove me from your list remove my number unsubscribe opt out opt me out no longer wish to be contacted stop contacting me lose my number never call me
Matching is deliberately generous, and it is generous in one direction on purpose: hearing an opt-out that was not quite one costs you a contact, and missing one that was costs a call to somebody who asked you to stop. It works on either direction of call — somebody who rang in to say “stop calling me” has asked exactly as clearly as somebody who was called — and it takes the other party's number, never your own caller ID.
Consent records
Whether a consent record is required is set for the whole deployment; whether one exists is per number, and yours to record. An account calling only its own customers under an established relationship may legitimately not track per-number consent. Anything that looks like acquisition should.
/v1/calls/consentscurl -s -X POST https://voice.sphoro.com/v1/calls/consents \
-H "Authorization: Bearer $SPHORO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"number": "+919876543210",
"method": "web_form",
"evidence": "signup-8f31c2e4",
"granted_at": "2026-08-01T09:12:00Z",
"expires_at": "2027-08-01T09:12:00Z"
}'| Field | Type | Description |
|---|---|---|
numberrequired | string | The number the permission covers. |
methodrequired | string | How it was obtained — web_form, verbal_recorded,
contract. Free text, because the taxonomy differs by jurisdiction and by counsel.
Required, because a consent record with no method is not evidence of anything: the question
asked afterwards is “how did you obtain consent”, and “we have a row in a table” is not an
answer to it. |
evidenceoptional | string | What proves it — a form submission id, a recording URL, a contract reference. The claim that survives scrutiny is the evidence, not the flag. |
granted_atoptional | timestamp | When they agreed. Defaults to now. |
expires_atoptional | timestamp | When it lapses. Omit it and the consent does not expire — which is a claim worth making deliberately rather than by leaving a field out. |
Revoking
/v1/calls/consents/{number}Two things at once, because they are one event seen from two sides: the record is marked revoked, and the number goes on your do-not-call list. Somebody withdrawing permission has also asked not to be called, and recording the first without doing the second would keep calling them.
The record is marked, never deleted. That consent once existed and when it ended is itself the evidence, and deleting it destroys your own defence along with the row.
What stays yours
Everything below is real and none of it is done for you. It is listed rather than left unsaid, because a platform that implies it handles all of this is the reason somebody stops checking.
| Not done for you | What that means |
|---|---|
| Registry imports | There is no automatic sync with a national do-not-call registry. Entries arrive through the API above; scheduling that import is yours. |
| Registration where it is required | India's DLT regime and its equivalents elsewhere are registrations held by you, in your own name, and no API here substitutes for one. |
| Rules narrower than the window | Several US states are stricter than the federal hours, and the window here is one policy per deployment rather than one per state. |
| Mobile or landline | Some rules turn on which a number is. That needs a carrier lookup this platform does not perform. |
| Reassigned numbers | A number whose owner has changed is a known trap. No reassignment database is consulted. |
| How often you call | Attempt caps and frequency limits are not enforced. Two campaigns can reach the same person on the same day. |
| What the agent says | Identifying yourself, saying who is calling and why, and disclosing a recording are your prompt and your greeting — not a setting. |
Check it before you dial a list
- You have run one real number through
/v1/calls/compliance/checkand read the verdict, rather than assuming the checks are on. - Your do-not-call list has whatever you are already obliged to honour in it — nothing imports it for you.
- Your greeting says who is calling. Nothing on this page does that for you.
- Someone who is not us has told you which rules apply to the calls you are about to place.
Back to the overview, or step 3 for placing the call itself.