PhoneNumber
Phone numbers: a real number your agents answer and call from.
[pick] = noclick.PhoneNumber.available(country="US", area_code="415", limit=1)
number = noclick.PhoneNumber.buy(pick, max_monthly_usd=5)
number.answer_with(host) # inbound calls reach this agent
call = number.call("+14155550100", goal="Confirm Friday's 3pm booking.").wait()
print(call.text) # the transcript, one turn per lineNumbers in the countries NoClick sells in (PhoneNumber.coverage()), each at
its own price: billed monthly, and once when bought where its carrier charges
that; available on accounts with the phone feature (noclick.NotAvailable
otherwise). A number whose country asks for documents is bought with them
(PhoneNumber.upload_document, then requirements= on buy): the
carrier reviews them first, so buy returns a PhoneNumberOrder that
becomes the number once the carrier approves it. A number you keep with your own
phone provider is connected through that provider account
(PhoneNumber.connect), which bills it. Numbers have names
(PhoneNumber.from_name). The answering agent can transfer a call to a
person, a caller proven to be one of your users runs as that user,
callers says who gets through, calls both ways are listed, and a number can
record its calls. Invite-only beta: the agent texting the caller
(answer_with(texts=True), under a TextingRegistration of your business
with the US carriers), porting numbers in (PhoneNumber.port_in) and moving
one away (PhoneNumber.transfer_out).
class PhoneNumberA number you own. release() gives it back (billing stops); a number you keep with
your own phone provider is disconnect()ed instead.
- Like every live object, it also has hydrate(), refresh() and raw.
| id | str | |
| name | str | None | What your code calls it, unique among its user's numbers (None for one bought without a name). |
| number | str | The number in E.164 form. |
| source | str | None | Where it came from: |
| connection | str | None | The phone provider connection a connected number runs through, by name (None otherwise). |
| agent_id | str | None | The agent that answers inbound calls (None if none). |
| transfer_to | str | None | Where the answering agent's |
| texts | bool | None | Whether the answering agent can text the caller ( |
| callers | str | None | Who gets through: |
| record_calls | bool | None | Whether its calls, both ways, are recorded. |
| monthly_usd | float | None | What NoClick bills for it a month, in US dollars (None for a connected number, which its provider bills). |
| user_id | str | None | |
| created_at | Any | |
| released | bool | Whether |
availablestaticasync
noclick.PhoneNumber.available(
*, country=None, type=None, area_code=None, locality=None, region=None,
contains=None, starts_with=None, ends_with=None, features=None, limit=None,
user=..., client=None
) -> list[AvailableNumber]Numbers for sale in country (US by default) of type (local by
default: also mobile, toll_free, national, shared_cost), narrowed by
area_code, locality (a city), region, contains (digits, letters or
* wildcards), starts_with and ends_with (digits of the national number),
and features (voice, or voice_sms for numbers that take texts too).
NoneNoneNoneNoneNoneNoneNoneNoneNoneReturn at most this many.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Up to limit numbers (10 by default, at most 30), each with its
monthly_usd and upfront_usd, the locality and region it's local
to, what it can do (capabilities), what its country asks before it's sold
(requirements) and whether it can text (texting).
for pick in noclick.PhoneNumber.available(area_code="415", limit=5):
print(pick.number, pick.locality, pick.monthly_usd, pick.upfront_usd)
with_digits = noclick.PhoneNumber.available(contains="2024") # numbers containing these digits
london = noclick.PhoneNumber.available(country="GB", locality="London")coveragestaticasync
noclick.PhoneNumber.coverage(*, user=..., client=None) -> list[NumberCoverage]The countries numbers are for sale in, with the kinds of number sold in each. Right after a
deploy, while that is still being found out, the API answers 503 with Retry-After, which the
client retries on its own.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A noclick.NumberCoverage per country: its ISO code, the kinds of number in stock
there (types), the kinds that can text (texting_types), the kinds bought with
documents the carrier reviews first (documents_types) and the kinds that ask a step
that can't be taken here (steps_types), which can't be bought through the API yet.
for country in noclick.PhoneNumber.coverage():
print(country.country, country.types)placesstaticasync
noclick.PhoneNumber.places(
country, *, type=None, user=..., client=None
) -> list[NumberPlace]Where in country numbers of type (local by default) are for sale:
cities, or area codes in North America.
NoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A noclick.NumberPlace per place: its kind (locality or area_code),
its value and the region it's in. Empty where the carrier doesn't say.
[first, *_] = noclick.PhoneNumber.places("GB")
numbers = noclick.PhoneNumber.available(country="GB", locality=first.value)buyclassmethodasync
noclick.PhoneNumber.buy(
number, *, max_monthly_usd, max_upfront_usd=None, country=None, type=None,
address=None, requirements=None, name=None, user=..., client=None
) -> PhoneNumber | PhoneNumberOrderBuy number (a string or an AvailableNumber, whose country and
type it takes). The purchase is refused (noclick.PriceExceeded) if it
costs more than max_monthly_usd a month or max_upfront_usd once (by
default, the upfront_usd an AvailableNumber showed; 0 for a number passed
as a string). A number whose country asks who holds it takes address
(a noclick.NumberHolder, with a phone where a requirement's kind is
contact). One whose country asks for documents takes requirements: each
textual and document requirement's answer by its id (the text, or a
noclick.NumberDocument from PhoneNumber.upload_document).
name is what your code calls it, unique among its user's numbers
(noclick.Conflict with existing_id when taken, before anything is bought);
PhoneNumber.from_name finds it by that.
NoneNoneNoneNoneNoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The number, now yours. Its first month is charged now and it renews monthly
until you release() it. A number whose country asks for documents comes back
as a noclick.PhoneNumberOrder instead, in the carrier's review (days,
sometimes): nothing is charged until it completes, and order.phone_number()
is the number once it has.
Pick one that's for sale and buy it with a ceiling on the monthly price:
[pick] = noclick.PhoneNumber.available(area_code="415", limit=1)
number = noclick.PhoneNumber.buy(pick, max_monthly_usd=5)
print(number.number, number.monthly_usd)Nothing is bought when the price is above your ceiling:
[pick] = noclick.PhoneNumber.available(area_code="415", limit=1)
try:
number = noclick.PhoneNumber.buy(pick, max_monthly_usd=1)
except noclick.PriceExceeded as err:
print(err.message)A number whose country asks for documents, filed for the carrier's review:
[pick] = noclick.PhoneNumber.available(country="IT", limit=1)
asked = {item["name"]: item for item in pick.requirements["items"]}
passport = noclick.PhoneNumber.upload_document(
"passport.pdf", requirement=asked["Identity Document"], country="IT")
order = noclick.PhoneNumber.buy(
pick, max_monthly_usd=5, max_upfront_usd=5,
address=noclick.NumberHolder(name="Giulia Rossi", street="Via Roma 1", city="Milano",
postal_code="20121", country="IT", phone="+393471234567"),
requirements={asked["Identity Document Type"]["id"]: "Passport",
asked["Identity Document"]["id"]: passport})
print(order.status) # in_review
order.refresh() # later: completed, rejected (order.reason) or failed
if order.status == "completed":
number = order.phone_number()upload_documentstaticasync
noclick.PhoneNumber.upload_document(
file, *, requirement, country, type='local', filename=None, user=..., client=None
) -> NumberDocumentUpload a document one of a number's requirements asks for (a requirement of kind
document in country's numbers of type): file is its bytes, or a path to
read; filename defaults to the path's name and is required for bytes. requirement
is the requirement, or its id. A PDF, PNG or JPEG, up to 20 MB. The carrier holds the
file; NoClick keeps only its name and what it's for.
'local'NoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A noclick.NumberDocument to pass in requirements when you buy. One no purchase
names within 25 minutes is gone; one a purchase named can be named again.
[pick] = noclick.PhoneNumber.available(country="IT", limit=1)
[asked] = [item for item in pick.requirements["items"] if item["kind"] == "document"]
passport = noclick.PhoneNumber.upload_document("passport.pdf", requirement=asked, country="IT")
print(passport.id, passport.requirement_name)connectablestaticasync
noclick.PhoneNumber.connectable(
connection, *, user=..., client=None
) -> list[ConnectableNumber]The numbers in your phone provider account that connection (a noclick.Connection
or its name, in the current user) can route. The connection is the provider account's own:
connected with the account's id and secret, since NoClick checks the provider's signed
calls with that secret, which a restricted API key can't do (a connection holding one
raises noclick.InvalidRequest naming connection).
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Each number (up to 1,000) with its label, whether its calls go somewhere today
(routed_elsewhere), and, when it's already connected to NoClick, the number it's
connected as (phone_number_id) and the agent answering it (agent_id).
provider = noclick.Connection.from_name("phone-provider")
for pick in noclick.PhoneNumber.connectable(provider):
if pick.phone_number_id:
print(pick.number, "is already connected, answered by", pick.agent_id)
else:
print(pick.number, pick.label, "(answered elsewhere today)" if pick.routed_elsewhere else "")connectclassmethodasync
noclick.PhoneNumber.connect(
number, *, connection, name=None, record_calls=False, user=..., client=None
) -> PhoneNumberConnect number, which you keep with your own phone provider, through
connection (that account's connection: see connectable). NoClick remembers where
its calls go today and leaves them there until answer_with routes them to an agent;
disconnect() puts them back. answer_with, call, calls and recording work
on it as on a bought number, with calls placed through the provider account. The
provider bills the number and its minutes to you directly; NoClick bills only what it
adds (the agent's time on a call). Inside as_user it's the user's number, on the
user's connection. A number already connected, or one another NoClick workflow answers,
raises noclick.Conflict (its existing_id when it's yours): one number, one agent.
A number on a SIP trunk can't be connected (noclick.InvalidRequest).
NoneFalseWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The number, source connected, with no agent yet.
provider = noclick.Connection.from_name("phone-provider")
number = noclick.PhoneNumber.connect("+14155550123", connection=provider, name="front-desk")
number.answer_with(noclick.Agent.from_name("receptionist"))port_ininvite-only betaclassmethodasync
noclick.PhoneNumber.port_in(
numbers, *, account_holder, account_name, service_address, account_number,
max_monthly_usd, billing_number=None, pin=None, bill=None, name=None,
requested_at=None, user=..., client=None
) -> PortRequestPort US local numbers in from their carrier, so they become NoClick numbers: one, or
several on one account at that carrier (one letter of authorization covers them).
account_holder (a noclick.Human or an email: the person authorized on the
account) signs the letter on a NoClick page, branded like your other pages. The rest is
how the current carrier knows the account, as its bill shows it: account_name (the
name on the account, often a business, not the signer), service_address
(street, city, region, postal_code), account_number,
billing_number (the account's main number; the first number when omitted), pin
where the carrier has one (a mobile account's transfer PIN), and bill (a recent
bill's PDF bytes) where it asks for one. requested_at (a timezone-aware
datetime) is when you'd like them to move; the carrier sets the dates. Each is
billed monthly once moved, like a bought number, refused (noclick.PriceExceeded,
nothing sent) above max_monthly_usd. name names a single number. A number you
connected through your own phone provider is disconnected first (noclick.InvalidRequest
otherwise); for one kept with Twilio, the account's name is "Twilio, Inc.", its number
the last 8 characters of your Account SID, and the PIN comes from Twilio's support.
NoneNoneNoneNoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The port request: an operation in awaiting_human until the account holder signs
(sign_url), then submitted and scheduled (its step), whose .wait()
gives the PhoneNumber objects once they've moved. They keep working with their carrier
until then.
port = noclick.PhoneNumber.port_in(
"+14155550188",
account_holder="dana@harbordental.test",
account_name="Harbor Dental LLC",
service_address={"street": "1 Ferry Building", "city": "San Francisco", "region": "CA",
"postal_code": "94111"},
account_number="8812-4410-09",
pin="4410",
max_monthly_usd=5,
name="front-desk",
)
print(port.status, port.sign_url) # awaiting_human, the page Dana signs
[number] = port.wait()
print(number.source) # portedfrom_nameclassmethod
noclick.PhoneNumber.from_name(
name, *, create_if_missing=False, area_code=None, max_monthly_usd=None,
max_upfront_usd=None, connect=None, connection=None, record_calls=..., user=...,
client=None
) -> PhoneNumberA lazy reference to the number called name in the current (or given) user.
With create_if_missing=True your code is the definition: the first time it's used
with none of that name, NoClick buys a US local number in area_code (any US area code
when omitted) for at most max_monthly_usd a month and max_upfront_usd once
(noclick.PriceExceeded above either) and names it, or with connect= connects that number of yours through
connection instead (as PhoneNumber.connect); an existing number whose
record_calls differs is updated to match. The number itself never changes, so the
same code keeps the same number.
FalseNoneNoneNoneNoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A number that loads (or is bought, or brought in step with your definition) the first time it's used. Nothing is sent yet.
Refer to a number by name:
number = noclick.PhoneNumber.from_name("front-desk")
print(number.number)Or own one idempotently: the first run buys it, every run after finds it:
number = noclick.PhoneNumber.from_name(
"front-desk", create_if_missing=True, area_code="415", max_monthly_usd=5, max_upfront_usd=5,
)
number.answer_with(noclick.Agent.from_name("receptionist"))Or the number you already have, connected through your phone provider account:
provider = noclick.Connection.from_name("phone-provider")
number = noclick.PhoneNumber.from_name(
"front-desk", create_if_missing=True, connect="+14155550123", connection=provider,
)from_idclassmethod
noclick.PhoneNumber.from_id(number_id, *, client=None, user=...) -> PhoneNumberA lazy reference to a number you own, by id.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
A number that loads the first time you read a field. Nothing is sent yet.
number = noclick.PhoneNumber.from_id("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
for call in number.calls():
print(call.to, call.status)getclassmethodasync
noclick.PhoneNumber.get(number_id, *, user=..., client=None) -> PhoneNumberLoad a number by id now; an unknown id, or one you released, raises noclick.NotFound.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The number, loaded.
number = noclick.PhoneNumber.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
print(number.number, number.agent_id)listclassmethodasync
noclick.PhoneNumber.list(
*, limit=None, user_filter=None, user=..., client=None
) -> list[PhoneNumber]The numbers you own (the current user's inside as_user, or those user_filter covers).
Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching number, newest first, fetched page by page. Released numbers aren't listed.
for number in noclick.PhoneNumber.list():
print(number.number, number.agent_id)
theirs = noclick.PhoneNumber.list(user_filter="user_42") # one user's, by your id for themlist_pageclassmethodasync
noclick.PhoneNumber.list_page(
*, cursor=None, limit=None, user_filter=None, user=..., client=None
) -> PageOne page of numbers; pass next_cursor back as cursor.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A page whose data holds the numbers and whose next_cursor is None on the last page.
page = noclick.PhoneNumber.list_page(limit=50)
while True:
for number in page.data:
print(number.number)
if page.next_cursor is None:
break
page = noclick.PhoneNumber.list_page(limit=50, cursor=page.next_cursor)releaseasync
number.release() -> NoneGive the number back; it stops ringing and billing. A connected number isn't NoClick's
to give back: it raises noclick.Conflict, so disconnect() it.
number = noclick.PhoneNumber.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
number.release()
print(number.released) # Truedisconnectasync
number.disconnect() -> NoneHand a connected number back to its provider account: NoClick takes it off its agent,
puts back where its calls went before it was connected, and it leaves your list. A
bought number raises noclick.Conflict: release() it.
number = noclick.PhoneNumber.from_name("front-desk")
number.disconnect()
print(number.released) # Trueupdateasync
number.update(*, name=..., record_calls=...) -> PhoneNumberRename it (name=None removes its name; one another of its user's numbers
has raises noclick.Conflict), or start or stop recording its calls
(record_calls), from the next call. A recording holds both sides of a call
in one file, and the caller hears that the call is recorded before the agent speaks.
This number, updated. A field you leave out is unchanged.
number = noclick.PhoneNumber.from_name("front-desk")
number.update(name="reception")
number.update(record_calls=True)answer_withasync
number.answer_with(
agent, *, transfer_to=None, texts=False, callers='anyone', conversation=None
) -> PhoneNumberRoute inbound calls to agent (an noclick.Agent or id), an agent on the
sdk harness, whose tools and connections work during a call as in a message; one on
a coding harness can't answer calls (noclick.NotAvailable).
conversation is the agent's conversation each call lands in, a template over
{caller} and {number}: "owner" puts every call in the same conversation as
agent.message(..., conversation="owner"), so a call, an email and a text from one
person are one thread. Omitted, each caller has a conversation of their own on the number.
transfer_to (a phone number in E.164 form, or a noclick.Human with a
phone) gives the agent a transfer_call tool that hands the caller to that
number once the agent has said so; who picks up hears why first, and a transfer
nobody picks up comes back to the agent, which is told.
Invite-only beta: texts=True gives it a send_text tool that texts the
caller, and only the caller (a link, a confirmation, an address), after asking and
hearing a yes. Each text is signed with your brand (Harbor Dental: ...), the first
to a caller says how to opt out, and a caller who texted STOP is never texted again.
Texting from a US number needs your business registered with the US carriers
(noclick.TextingRegistration); until that's approved texts=True raises
noclick.ActionRequired naming it, and a number that can't text (one kept with your
own phone provider) raises noclick.NotAvailable. Until texting ships,
texts=True raises noclick.NotAvailable.
On an account number, a caller whose number is one of your users' verified phones
(User.verify_phone), with the carrier attesting the caller id (STIR/SHAKEN level
A), runs as that user, in their connections, whatever callers says; any other
call runs as your account. A user's own number runs every call as that user.
callers only says who gets through: "anyone" (the default), "users" (only
callers proven to be one of your users) or "owner" (only the number's own user,
or your account's owner for an account number, by the phone verified on their
NoClick account, attested the same way). Anyone else hears that the number doesn't
take their call, and the call is listed as refused. Each call to answer_with
sets these afresh: an option you leave out goes back to its default.
None'anyone'NoneThis number, with agent_id naming the agent that now answers it.
Every call to the number then reaches that agent. A number rings one agent
at a time, so calling answer_with again moves it:
number = noclick.PhoneNumber.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
receptionist = noclick.Agent.from_name(
"receptionist",
create_if_missing=True,
instructions="You answer calls for Harbor Dental: opening hours, directions, and what to bring.",
)
number.answer_with(receptionist)Let it hand callers to a person:
number = noclick.PhoneNumber.from_name("front-desk")
receptionist = noclick.Agent.from_name("receptionist")
number.answer_with(
receptionist, transfer_to=noclick.Human(email="dana@harbordental.test", phone="+14155550142"),
)And text them a link (invite-only beta):
number = noclick.PhoneNumber.from_name("front-desk")
number.answer_with(noclick.Agent.from_name("receptionist"), transfer_to="+14155550142", texts=True)Answer only your users, each call acting for the user who called:
number = noclick.PhoneNumber.from_name("assistant-line")
number.answer_with(noclick.Agent.from_name("concierge"), callers="users")Or a user's own line that only they get through to:
with noclick.as_user("user_42"):
number = noclick.PhoneNumber.from_name("my-assistant")
number.answer_with(noclick.Agent.from_name("assistant"), callers="owner")callasync
number.call(to, *, goal, agent=None) -> OperationCall to with the number's answering agent working toward
goal (agent=, if given, must be that agent).
NoneThe call goes out through the agent that answers the number, so route it
with answer_with first; a number with no agent raises noclick.ActionRequired:
number = noclick.PhoneNumber.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
call = number.call(
"+14155550100",
goal="Confirm Friday's 3pm booking, or find a new time that works.",
).wait()
print(call.status)
print(call.text) # the transcript, one turn per linecallsasync
number.calls(*, direction=None, since=None, limit=None) -> list[PhoneCall]The calls on this number, newest first: the calls it answered with the ones it
placed. direction ("inbound" or "outbound") keeps one kind, since (a
timezone-aware datetime) those that started at or after it, and limit the
newest that many.
NoneNoneReturn at most this many.
None)The matching calls, each with its direction and its transcript once it has
ended (and recording_url when the number records its calls).
See what the number placed:
number = noclick.PhoneNumber.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
for call in number.calls():
print(call.to, call.status, call.seconds, call.cost_usd)Or what callers asked it this week:
from datetime import datetime, timedelta, timezone
number = noclick.PhoneNumber.from_name("front-desk")
week_ago = datetime.now(timezone.utc) - timedelta(days=7)
for call in number.calls(direction="inbound", since=week_ago, limit=50):
print(call.from_number, call.seconds, call.text)text_messagesinvite-only betaasync
number.text_messages(*, direction=None, since=None, limit=None) -> list[PhoneText]The texts its answering agent sent callers (send_text) and the texts the number
received, newest first. A reply doesn't wake the agent: read it here, or take
phone.text.received events. direction ("inbound" or "outbound") keeps
one kind, since (a timezone-aware datetime) those at or after it.
NoneNoneReturn at most this many.
None)The matching texts, each with its direction, text, status and cost.
number = noclick.PhoneNumber.from_name("front-desk")
for text in number.text_messages(direction="inbound", limit=20):
print(text.from_number, text.text, text.keyword)transfer_outinvite-only betaasync
number.transfer_out() -> TransferOutMove the number to another carrier: what that carrier asks for (the account it's on and a transfer PIN for it). Another carrier may take the number for 30 days from now; a request outside that window, or quoting another PIN, is refused, so nobody takes it without you. Once it moves, it leaves your numbers and its billing stops.
The transfer's details, with its pin and expires_at.
number = noclick.PhoneNumber.from_name("front-desk")
details = number.transfer_out()
print(details.account_name, details.account_number, details.pin, details.expires_at)AvailableNumber
class AvailableNumberA number you can buy: noclick.PhoneNumber.buy(available, max_monthly_usd=...).
monthly_usd it costs a month and upfront_usd once, when it's bought.
requirements["needs"] is what its country asks before it's sold: none,
address (pass address= to buy), documents (pass requirements=
to buy too, uploading each document with PhoneNumber.upload_document;
the carrier reviews them before the number is yours) or steps (it asks a
step that can't be taken here, so it can't be bought through the API yet).
requirements["items"] lists each thing asked: its id, kind
(address, contact, textual, document or step), name,
description, the carrier's example and, for a textual one that takes
only some answers, those values. texting: registration (a US number,
which texts under your business's US carrier registration), unavailable
(texting from its country isn't available yet) or none (it can't text).
| number | str | |
| monthly_usd | float | None | |
| locality | str | None | |
| region | str | None | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
| country | str | None | |
| type | str | None | |
| upfront_usd | float | None | |
| capabilities | list[str] | |
| requirements | dict[str, Any] | |
| texting | str | None |
PhoneCall
class PhoneCallOne call on a number, answered (direction inbound) or placed
(outbound). transcript (turns of {role, text, at}) is filled in
at hang-up; text reads it as lines. status is calling while it
runs and ends as completed, not_connected, refused (a caller the
number's callers turned away, error says why), failed or lost.
| id | str | None | |
| direction | str | None | |
| to | str | None | |
| from_number | str | None | |
| status | str | None | |
| goal | str | None | |
| agent_id | str | None | |
| transcript | list[dict[str, Any]] | |
| seconds | float | None | |
| cost_usd | float | None | What the call was charged, as your usage shows it ( |
| error | str | None | |
| created_at | Any | |
| ended_at | Any | |
| user_id | str | None | The user the call ran as: on an account number, a caller proven to be one of your users (their verified phone, attested by the carrier), whatever the number's |
| recording_url | str | None | A signed link to the call's recording, both sides in one file, valid for an hour from when you read the call ( |
| transfer | dict[str, Any] | None | Where the agent handed the caller ( |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
| text | str | The transcript as |
ConnectableNumber
class ConnectableNumberA number in your phone provider account, from noclick.PhoneNumber.connectable:
connect it with noclick.PhoneNumber.connect(connectable, connection=...).
label is the provider account's own name for it. routed_elsewhere means its calls go
somewhere today (a URL, an application or a SIP trunk): answering it takes them over, and
disconnect() puts that back. phone_number_id is the NoClick number it's already
connected as, and agent_id the agent answering it (both None when it isn't connected):
connecting it again raises noclick.Conflict, since one number rings one agent.
| number | str | |
| label | str | None | |
| routed_elsewhere | bool | |
| phone_number_id | str | None | |
| agent_id | str | None | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
PortRequestinvite-only beta
class PortRequest(Operation)Numbers moving to NoClick from their carrier on one letter of authorization: the
operation PhoneNumber.port_in returns. It waits in awaiting_human until the
account holder signs the letter at sign_url, then runs (step is submitted,
then scheduled once the carrier has given every number its date, port_at the
latest), and wait() gives the PhoneNumber objects (source ported) once they've
moved. A rejection fails it with the carrier's reason: noclick.InvalidRequest whose
field names what didn't match its records (account_name, account_holder,
service_address, account_number, billing_number, pin, numbers or
requested_at); retry() sends corrected details. cancel() works until the
carrier has committed to a date. The numbers keep working with their carrier until
they move.
- Inherits from Operation.
| number | str | None | The first number on the letter, in E.164 form. |
| numbers | list[dict[str, Any]] | None | Each number on the letter, with its own |
| port_at | Any | When the last number moves, once every one is |
| monthly_usd | float | None | What each number costs a month once it's yours, in US dollars. |
| sign_url | str | None | While |
listclassmethodasync
noclick.PortRequest.list(*, limit=None, **filters) -> list[PortRequest]Port requests (kind phone.port_in), newest first; takes Operation.list's
filters except kind.
Return at most this many.
None)Every matching port request (or the first limit), each with its numbers,
step, port_at and sign_url.
for port in noclick.PortRequest.list(status="live", user_filter=noclick.ALL_USERS):
print(port.number, port.status, port.step, port.port_at)list_pageclassmethodasync
noclick.PortRequest.list_page(*, cursor=None, limit=None, **filters) -> PageOne page of port requests; pass next_cursor back as cursor.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)A page whose data holds the port requests and whose next_cursor is None on the last page.
page = noclick.PortRequest.list_page(status="failed", limit=50)
for port in page.data:
print(port.number, port.error)retryasync
port_request.retry(
*, account_holder=None, account_name=None, service_address=None,
account_number=None, billing_number=None, pin=None, bill=None, requested_at=None
) -> PortRequestSend a port the carrier refused again with corrected details, for the numbers that
haven't moved; a detail you leave out keeps what it had. A change to what the letter
says (the account holder, the account's name, the service address, the account or
billing number), or a refusal of the name it was signed with, asks the account holder
to sign again; a new pin, bill or requested_at goes straight back.
NoneNoneNoneNoneNoneNoneNoneNoneA new port request for the same numbers, waiting as port_in's does.
port = noclick.PhoneNumber.port_in(
"+14155550188", account_holder="dana@harbordental.test", account_name="Harbor Dental LLC",
service_address={"street": "1 Ferry Building", "city": "San Francisco", "region": "CA",
"postal_code": "94111"},
account_number="8812-4410-09", max_monthly_usd=5,
)
try:
numbers = port.wait()
except noclick.InvalidRequest as err:
print(err.field, err.message) # account_number, The account number doesn't match.
port = port.retry(account_number="8812-4410-07")TextingRegistrationinvite-only beta
class TextingRegistrationYour business registered with the US carriers, which texting from a US number needs:
the brand the carriers' registry verifies, and the campaign NoClick writes for it (a voice
agent texting a caller one thing they asked for, after a spoken yes). One per scope: your
account's, or one of your users' (a user's own number texts under its user's registration
when there is one, else the account's). Review takes days to weeks; status follows it:
pending, awaiting_human (its contact must answer the registry: a public company's
verification email, or a sole proprietor's texted code, sent back with verify),
in_review, then approved, rejected (status_reason, and fields names the
facts to correct with update) or suspended. delete() stops texting and its
monthly fee.
- Like every live object, it also has hydrate(), refresh() and raw.
| id | str | |
| status | str | None |
|
| status_reason | str | None | The reviewer's words, when it refused or suspended it. |
| fields | list[str] | None | The facts a refusal named, to correct with |
| legal_name | str | None | |
| brand_name | str | None | What its texts are signed with: |
| entity_type | str | None | |
| industry | str | None | |
| website | str | None | |
| tax_id | str | None | The EIN, its last four digits shown. |
| privacy_url | str | None | |
| terms_url | str | None | |
| setup_usd | float | None | Charged once, as the registry charges it: the brand when registered, the campaign's review and first months when it opens. |
| monthly_usd | float | None | The campaign's monthly fee, after its first months. |
| segment_usd | float | None | What a text costs a segment (160 characters), sent or received, before the recipient's network fee, which is charged as reported. |
| user_id | str | None | |
| approved_at | Any | |
| created_at | Any |
createclassmethodasync
noclick.TextingRegistration.create(
*, legal_name, entity_type, industry, address, website, contact, privacy_url,
terms_url, max_setup_usd, max_monthly_usd, tax_id=None, brand_name=None,
stock=None, user=..., client=None
) -> TextingRegistrationRegister the business with the US carriers, as the legal entity on its tax records:
entity_type is private_company, public_company, nonprofit,
government or sole_proprietor; tax_id its EIN (none for a sole
proprietor); industry one of the registry's (healthcare, retail,
professional, ...); address its US address (street, city, region,
postal_code); website, privacy_url and terms_url pages anyone can open
(the privacy policy must say phone numbers aren't shared); contact the person the
registry contacts (name, email, phone: your business's own, never NoClick's);
stock (symbol, exchange) for a public company. brand_name is what texts
are signed with (the legal name when omitted). Refused (noclick.PriceExceeded, nothing
registered) above max_setup_usd once or max_monthly_usd a month.
NoneNoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The registration, pending; its brand's fee is charged now. A scope that has one
raises noclick.Conflict with its existing_id.
registration = noclick.TextingRegistration.create(
legal_name="Harbor Dental LLC", brand_name="Harbor Dental", entity_type="private_company",
tax_id="12-3456789", industry="healthcare", website="https://harbordental.test",
address={"street": "1 Ferry Building", "city": "San Francisco", "region": "CA",
"postal_code": "94111"},
contact={"name": "Dana Ruiz", "email": "dana@harbordental.test", "phone": "+14155550142"},
privacy_url="https://harbordental.test/privacy", terms_url="https://harbordental.test/terms",
max_setup_usd=40, max_monthly_usd=5,
)
print(registration.status, registration.setup_usd, registration.monthly_usd)from_idclassmethod
noclick.TextingRegistration.from_id(
registration_id, *, client=None, user=...
) -> TextingRegistrationA lazy reference to a registration by id.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
A registration that loads the first time you read a field. Nothing is sent yet.
registration = noclick.TextingRegistration.from_id("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
print(registration.status)getclassmethodasync
noclick.TextingRegistration.get(
registration_id, *, user=..., client=None
) -> TextingRegistrationLoad a registration by id now; an unknown or deleted one raises noclick.NotFound.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The registration, loaded.
registration = noclick.TextingRegistration.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
print(registration.status, registration.status_reason)listclassmethodasync
noclick.TextingRegistration.list(
*, limit=None, user_filter=None, user=..., client=None
) -> list[TextingRegistration]The registrations of the current user (or, without one, the account's), or those
user_filter covers.
Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching registration, newest first. Deleted ones aren't listed.
for registration in noclick.TextingRegistration.list(user_filter=noclick.ALL_USERS):
print(registration.brand_name, registration.status)updateasync
texting_registration.update(**corrections) -> TextingRegistrationResubmit a refused registration (or one waiting on its contact) with corrected
facts, named as create names them; a fact you leave out keeps what it had. A
corrected privacy_url or terms_url opens a new campaign, whose review and first
months are charged again.
This registration, back in review.
registration = noclick.TextingRegistration.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
if registration.status == "rejected" and "tax_id" in registration.fields:
registration.update(tax_id="98-7654321")verifyasync
texting_registration.verify(code) -> TextingRegistrationA sole proprietor's contact sends back the code the registry texted them
(noclick.InvalidRequest naming code when it's wrong or expired).
This registration, its contact verified.
registration = noclick.TextingRegistration.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
registration.verify("482913")deleteasync
texting_registration.delete() -> NoneStop texting under it: its numbers stop texting, its campaign closes and its monthly fee ends.
noclick.TextingRegistration.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48").delete()PhoneTextinvite-only beta
class PhoneTextA text a number's agent sent a caller (direction outbound) or one the number
received (inbound). keyword is stop, start or help when a received
text was one: STOP stops texts to its sender, START lets them through again, and both
are answered in your business's name. status is sent, then delivered or
failed, for one sent; received for one received. cost_usd is what it was
charged: its segments, and the recipient's network fee once it's reported.
| id | str | None | |
| direction | str | None | |
| from_number | str | None | |
| to | str | None | |
| text | str | None | |
| keyword | str | None | |
| status | str | None | |
| segments | int | None | |
| cost_usd | float | None | |
| call_id | str | None | |
| error | str | None | |
| created_at | Any | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
TransferOutinvite-only beta
class TransferOutWhat the carrier you're moving a number to asks for, from
PhoneNumber.transfer_out: the account the number is on as NoClick's carrier's
records show it, and a transfer pin for this number, good until expires_at.
Until then another carrier may take the number; after it, a request is refused.
| phone_number_id | str | None | |
| number | str | None | |
| account_name | str | None | |
| account_number | str | None | |
| pin | str | None | |
| service_address | str | None | |
| expires_at | Any | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
NumberHolder
class NumberHolderWho a number is registered to, for a country that asks who holds it (one of its
requirements' kind is address or contact): first and last name (and the
business's, for a business's number) at a postal address; region is the
state, province or county where the country has one, country ISO alpha-2.
phone (E.164) is a number to contact them on, required where one of the
requirements' kind is contact.
| name | str | |
| street | str | |
| city | str | |
| postal_code | str | |
| country | str | |
| business_name | str | None | |
| street2 | str | None | |
| region | str | None | |
| phone | str | None |
NumberCoverage
class NumberCoverageA country numbers are for sale in, the kinds in stock there, the kinds that can text, the kinds
bought with documents the carrier reviews first (documents_types) and the kinds that ask a step
that can't be taken here (steps_types: these can't be bought through the API yet).
| country | str | |
| types | list[str] | |
| texting_types | list[str] | |
| documents_types | list[str] | |
| steps_types | list[str] |
NumberPlace
class NumberPlaceA place numbers of a kind are for sale in: a city (locality), or an area code in North America.
| kind | str | |
| value | str | |
| region | str |
NumberDocument
class NumberDocumentA document you uploaded for one of a number's requirements (PhoneNumber.upload_document): pass
it (or its id) in requirements when you buy. The carrier holds the file; NoClick keeps its
name and what it's for. One no purchase names within 25 minutes is gone; one a purchase named can be
named again for your next number.
| id | str | |
| requirement_id | str | |
| requirement_name | str | |
| filename | str | |
| uploaded_at | Any | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
PhoneNumberOrder
class PhoneNumberOrderA number ordered while the carrier reviews the documents filed for it (PhoneNumber.buy of a
number whose country asks for them). Nothing is yours, or charged, until it completes:
status is in_review, rejected (the carrier sent it back: reason says why;
resubmit corrections before deadline), completed (phone_number() is the number) or
failed (it ended without the number; nothing was charged).
- Like every live object, it also has hydrate(), refresh() and raw.
| id | str | |
| number | str | The number ordered, in E.164 form. |
| country | str | None | |
| type | str | None | |
| status | str |
|
| reason | str | None | The carrier's words when it sent the order back or ended it (None otherwise). |
| deadline | Any | When a sent-back order must be corrected by. |
| monthly_usd | float | None | What the number costs a month, once it's yours. |
| upfront_usd | float | None | What it costs once, charged when the order completes. |
| phone_number_id | str | None | The number, once the order completed. |
| requirements | dict[str, Any] | None | While it's open: what its country asks, which a |
| created_at | Any | |
| updated_at | Any |
retrieveclassmethodasync
noclick.PhoneNumberOrder.retrieve(
order_id, *, user=..., client=None
) -> PhoneNumberOrderLoad an order by id now; an unknown id raises noclick.NotFound.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The order, as it stands.
order = noclick.PhoneNumberOrder.retrieve("5f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f")
print(order.status, order.reason)resubmitasync
phone_number_order.resubmit(*, address=None, requirements=None) -> PhoneNumberOrderFile corrected answers for an order the carrier sent back (status rejected), for its
review again: address and requirements as PhoneNumber.buy takes them. Any other
order raises noclick.Conflict.
NoneNoneThis order, in_review again.
order = noclick.PhoneNumberOrder.retrieve(order_id)
if order.status == "rejected":
print(order.reason)
clearer = noclick.PhoneNumber.upload_document("passport-full.pdf", requirement=passport_id,
country="IT")
order.resubmit(address=holder, requirements={type_id: "Passport", passport_id: clearer})phone_numberasync
phone_number_order.phone_number() -> PhoneNumberThe number, once the order completed; noclick.Conflict while it hasn't (refresh() first).
The noclick.PhoneNumber the order became.
order.refresh()
if order.status == "completed":
number = order.phone_number()
number.answer_with(noclick.Agent.from_name("receptionist"))