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 line

Numbers 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 PhoneNumber

A number you own. release() gives it back (billing stops); a number you keep with your own phone provider is disconnect()ed instead.

Attributes
idstr
namestr | None

What your code calls it, unique among its user's numbers (None for one bought without a name).

numberstr

The number in E.164 form.

sourcestr | None

Where it came from: bought through NoClick or connected (kept with your own phone provider account, which bills it); ported (moved to NoClick from its carrier) is in invite-only beta.

connectionstr | None

The phone provider connection a connected number runs through, by name (None otherwise).

agent_idstr | None

The agent that answers inbound calls (None if none).

transfer_tostr | None

Where the answering agent's transfer_call tool sends a call (None: no such tool).

textsbool | None

Whether the answering agent can text the caller (send_text; invite-only beta).

callersstr | None

Who gets through: anyone, users or owner. It never changes who a call runs as.

record_callsbool | None

Whether its calls, both ways, are recorded.

monthly_usdfloat | None

What NoClick bills for it a month, in US dollars (None for a connected number, which its provider bills).

user_idstr | None
created_atAny
releasedbool

Whether release() gave the number back, or disconnect() handed it back to its provider account.

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).

Parameters
countrystr | Nonedefault None
typestr | Nonedefault None
area_codestr | int | Nonedefault None
localitystr | Nonedefault None
regionstr | Nonedefault None
containsstr | Nonedefault None
starts_withstr | Nonedefault None
ends_withstr | Nonedefault None
featuresstr | Nonedefault None
limitint | None

Return at most this many.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[AvailableNumber]

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).

Usage
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.

Parameters
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[NumberCoverage]

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.

Usage
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.

Parameters
countrystrrequired
typestr | Nonedefault None
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[NumberPlace]

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.

Usage
[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 | PhoneNumberOrder

Buy 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.

Parameters
numberstr | AvailableNumberrequired
max_monthly_usdfloatrequired
max_upfront_usdfloat | Nonedefault None
countrystr | Nonedefault None
typestr | Nonedefault None
addressNumberHolder | Nonedefault None
requirementsMapping[str, str | NumberDocument] | Nonedefault None
namestr | Nonedefault None
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
PhoneNumber | PhoneNumberOrder

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.

Usage

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
) -> NumberDocument

Upload 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.

Parameters
filebytes | str | os.PathLike[str]required
requirementstr | Mapping[str, Any]required
countrystrrequired
typestrdefault 'local'
filenamestr | Nonedefault None
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
NumberDocument

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.

Usage
[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).

Parameters
connectionAnyrequired
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[ConnectableNumber]

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).

Usage
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
) -> PhoneNumber

Connect 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).

Parameters
numberstr | ConnectableNumberrequired
connectionAnyrequired
namestr | Nonedefault None
record_callsbooldefault False
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
PhoneNumber

The number, source connected, with no agent yet.

Usage
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
) -> PortRequest

Port 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.

Parameters
numbersstr | list[str]required
account_holderAnyrequired
account_namestrrequired
service_addressdict[str, str]required
account_numberstrrequired
max_monthly_usdfloatrequired
billing_numberstr | Nonedefault None
pinstr | Nonedefault None
billbytes | Nonedefault None
namestr | Nonedefault None
requested_atAnydefault None
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
PortRequest

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.

Usage
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)                # ported

from_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
) -> PhoneNumber

A 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.

Parameters
namestrrequired
create_if_missingbooldefault False
area_codestr | int | Nonedefault None
max_monthly_usdfloat | Nonedefault None
max_upfront_usdfloat | Nonedefault None
connectstr | Nonedefault None
connectionAnydefault None
record_callsAnyomit to leave it unchanged
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
PhoneNumber

A number that loads (or is bought, or brought in step with your definition) the first time it's used. Nothing is sent yet.

Usage

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=...) -> PhoneNumber

A lazy reference to a number you own, by id.

Parameters
number_idstrrequired
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
Returns
PhoneNumber

A number that loads the first time you read a field. Nothing is sent yet.

Usage
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) -> PhoneNumber

Load a number by id now; an unknown id, or one you released, raises noclick.NotFound.

Parameters
number_idstrrequired
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
PhoneNumber

The number, loaded.

Usage
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).

Parameters
limitint | None

Return at most this many.

(default None)
user_filterAny

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.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[PhoneNumber]

Every matching number, newest first, fetched page by page. Released numbers aren't listed.

Usage
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 them

list_pageclassmethodasync

noclick.PhoneNumber.list_page(
    *, cursor=None, limit=None, user_filter=None, user=..., client=None
) -> Page

One page of numbers; pass next_cursor back as cursor.

Parameters
cursorstr | None

The next_cursor of the page before; omit it for the first page.

(default None)
limitint | None

Return at most this many.

(default None)
user_filterAny

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.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Page

A page whose data holds the numbers and whose next_cursor is None on the last page.

Usage
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() -> None

Give 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.

Usage
number = noclick.PhoneNumber.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
number.release()
print(number.released)   # True

disconnectasync

number.disconnect() -> None

Hand 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.

Usage
number = noclick.PhoneNumber.from_name("front-desk")
number.disconnect()
print(number.released)   # True

updateasync

number.update(*, name=..., record_calls=...) -> PhoneNumber

Rename 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.

Parameters
nameAnyomit to leave it unchanged
record_callsAnyomit to leave it unchanged
Returns
PhoneNumber

This number, updated. A field you leave out is unchanged.

Usage
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
) -> PhoneNumber

Route 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.

Parameters
agentAnyrequired
transfer_toAnydefault None
textsboolinvite-only betadefault False
callersstrdefault 'anyone'
conversationstr | Nonedefault None
Returns
PhoneNumber

This number, with agent_id naming the agent that now answers it.

Usage

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) -> Operation

Call to with the number's answering agent working toward goal (agent=, if given, must be that agent).

Parameters
tostrrequired
goalstrrequired
agentAnydefault None
Returns
Operation

An operation whose .wait() gives the PhoneCall at hang-up, with its transcript, status (completed or not_connected), length and cost.

Usage

The 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 line

callsasync

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.

Parameters
directionstr | Nonedefault None
sinceAnydefault None
limitint | None

Return at most this many.

(default None)
Returns
list[PhoneCall]

The matching calls, each with its direction and its transcript once it has ended (and recording_url when the number records its calls).

Usage

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.

Parameters
directionstr | Nonedefault None
sinceAnydefault None
limitint | None

Return at most this many.

(default None)
Returns
list[PhoneText]

The matching texts, each with its direction, text, status and cost.

Usage
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() -> TransferOut

Move 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.

Returns
TransferOut

The transfer's details, with its pin and expires_at.

Usage
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 AvailableNumber

A 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).

Attributes
numberstr
monthly_usdfloat | None
localitystr | None
regionstr | None
rawdict[str, Any]

The response JSON exactly as the API sent it.

countrystr | None
typestr | None
upfront_usdfloat | None
capabilitieslist[str]
requirementsdict[str, Any]
textingstr | None

PhoneCall

class PhoneCall

One 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.

Attributes
idstr | None
directionstr | None
tostr | None
from_numberstr | None
statusstr | None
goalstr | None
agent_idstr | None
transcriptlist[dict[str, Any]]
secondsfloat | None
cost_usdfloat | None

What the call was charged, as your usage shows it (None until it has ended and its charge is recorded, or when it never connected).

errorstr | None
created_atAny
ended_atAny
user_idstr | 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 callers; on a user's number, its user.

recording_urlstr | None

A signed link to the call's recording, both sides in one file, valid for an hour from when you read the call (None unless the number records its calls, or once the recording is deleted).

transferdict[str, Any] | None

Where the agent handed the caller (to) and whether they picked up (status answered with the transfer's seconds, or unanswered); None for a call nobody was handed.

rawdict[str, Any]

The response JSON exactly as the API sent it.

textstr

The transcript as role: text lines.

ConnectableNumber

class ConnectableNumber

A 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.

Attributes
numberstr
labelstr | None
routed_elsewherebool
phone_number_idstr | None
agent_idstr | None
rawdict[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.

Attributes
numberstr | None

The first number on the letter, in E.164 form.

numberslist[dict[str, Any]] | None

Each number on the letter, with its own status (draft, submitted, scheduled, rejected, ported) and port_at: a carrier can move them on different days.

port_atAny

When the last number moves, once every one is scheduled.

monthly_usdfloat | None

What each number costs a month once it's yours, in US dollars.

sign_urlstr | None

While awaiting_human: the page where the account holder signs the letter of authorization (it's emailed to them too; None otherwise).

listclassmethodasync

noclick.PortRequest.list(*, limit=None, **filters) -> list[PortRequest]

Port requests (kind phone.port_in), newest first; takes Operation.list's filters except kind.

Parameters
limitint | None

Return at most this many.

(default None)
**filtersAny
Returns
list[PortRequest]

Every matching port request (or the first limit), each with its numbers, step, port_at and sign_url.

Usage
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) -> Page

One page of port requests; pass next_cursor back as cursor.

Parameters
cursorstr | None

The next_cursor of the page before; omit it for the first page.

(default None)
limitint | None

Return at most this many.

(default None)
**filtersAny
Returns
Page

A page whose data holds the port requests and whose next_cursor is None on the last page.

Usage
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
) -> PortRequest

Send 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.

Parameters
account_holderAnydefault None
account_namestr | Nonedefault None
service_addressdict[str, str] | Nonedefault None
account_numberstr | Nonedefault None
billing_numberstr | Nonedefault None
pinstr | Nonedefault None
billbytes | Nonedefault None
requested_atAnydefault None
Returns
PortRequest

A new port request for the same numbers, waiting as port_in's does.

Usage
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 TextingRegistration

Your 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.

Attributes
idstr
statusstr | None

pending, awaiting_human, in_review, approved, rejected or suspended.

status_reasonstr | None

The reviewer's words, when it refused or suspended it.

fieldslist[str] | None

The facts a refusal named, to correct with update.

legal_namestr | None
brand_namestr | None

What its texts are signed with: Harbor Dental: ....

entity_typestr | None
industrystr | None
websitestr | None
tax_idstr | None

The EIN, its last four digits shown.

privacy_urlstr | None
terms_urlstr | None
setup_usdfloat | None

Charged once, as the registry charges it: the brand when registered, the campaign's review and first months when it opens.

monthly_usdfloat | None

The campaign's monthly fee, after its first months.

segment_usdfloat | None

What a text costs a segment (160 characters), sent or received, before the recipient's network fee, which is charged as reported.

user_idstr | None
approved_atAny
created_atAny

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
) -> TextingRegistration

Register 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.

Parameters
legal_namestrrequired
entity_typestrrequired
industrystrrequired
addressdict[str, str]required
websitestrrequired
contactdict[str, str]required
privacy_urlstrrequired
terms_urlstrrequired
max_setup_usdfloatrequired
max_monthly_usdfloatrequired
tax_idstr | Nonedefault None
brand_namestr | Nonedefault None
stockdict[str, str] | Nonedefault None
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
TextingRegistration

The registration, pending; its brand's fee is charged now. A scope that has one raises noclick.Conflict with its existing_id.

Usage
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=...
) -> TextingRegistration

A lazy reference to a registration by id.

Parameters
registration_idstrrequired
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
Returns
TextingRegistration

A registration that loads the first time you read a field. Nothing is sent yet.

Usage
registration = noclick.TextingRegistration.from_id("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
print(registration.status)

getclassmethodasync

noclick.TextingRegistration.get(
    registration_id, *, user=..., client=None
) -> TextingRegistration

Load a registration by id now; an unknown or deleted one raises noclick.NotFound.

Parameters
registration_idstrrequired
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
TextingRegistration

The registration, loaded.

Usage
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.

Parameters
limitint | None

Return at most this many.

(default None)
user_filterAny

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.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[TextingRegistration]

Every matching registration, newest first. Deleted ones aren't listed.

Usage
for registration in noclick.TextingRegistration.list(user_filter=noclick.ALL_USERS):
    print(registration.brand_name, registration.status)

updateasync

texting_registration.update(**corrections) -> TextingRegistration

Resubmit 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.

Parameters
**correctionsAny
Returns
TextingRegistration

This registration, back in review.

Usage
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) -> TextingRegistration

A sole proprietor's contact sends back the code the registry texted them (noclick.InvalidRequest naming code when it's wrong or expired).

Parameters
codestrrequired
Returns
TextingRegistration

This registration, its contact verified.

Usage
registration = noclick.TextingRegistration.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48")
registration.verify("482913")

deleteasync

texting_registration.delete() -> None

Stop texting under it: its numbers stop texting, its campaign closes and its monthly fee ends.

Usage
noclick.TextingRegistration.get("8e2f4b7a-1c3d-4a9e-b6f5-2d7c0e1a9b48").delete()

PhoneTextinvite-only beta

class PhoneText

A 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.

Attributes
idstr | None
directionstr | None
from_numberstr | None
tostr | None
textstr | None
keywordstr | None
statusstr | None
segmentsint | None
cost_usdfloat | None
call_idstr | None
errorstr | None
created_atAny
rawdict[str, Any]

The response JSON exactly as the API sent it.

TransferOutinvite-only beta

class TransferOut

What 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.

Attributes
phone_number_idstr | None
numberstr | None
account_namestr | None
account_numberstr | None
pinstr | None
service_addressstr | None
expires_atAny
rawdict[str, Any]

The response JSON exactly as the API sent it.

NumberHolder

class NumberHolder

Who 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.

Attributes
namestr
streetstr
citystr
postal_codestr
countrystr
business_namestr | None
street2str | None
regionstr | None
phonestr | None

NumberCoverage

class NumberCoverage

A 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).

Attributes
countrystr
typeslist[str]
texting_typeslist[str]
documents_typeslist[str]
steps_typeslist[str]

NumberPlace

class NumberPlace

A place numbers of a kind are for sale in: a city (locality), or an area code in North America.

Attributes
kindstr
valuestr
regionstr

NumberDocument

class NumberDocument

A 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.

Attributes
idstr
requirement_idstr
requirement_namestr
filenamestr
uploaded_atAny
rawdict[str, Any]

The response JSON exactly as the API sent it.

PhoneNumberOrder

class PhoneNumberOrder

A 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).

Attributes
idstr
numberstr

The number ordered, in E.164 form.

countrystr | None
typestr | None
statusstr

in_review, rejected, completed or failed.

reasonstr | None

The carrier's words when it sent the order back or ended it (None otherwise).

deadlineAny

When a sent-back order must be corrected by.

monthly_usdfloat | None

What the number costs a month, once it's yours.

upfront_usdfloat | None

What it costs once, charged when the order completes.

phone_number_idstr | None

The number, once the order completed.

requirementsdict[str, Any] | None

While it's open: what its country asks, which a resubmit answers.

created_atAny
updated_atAny

retrieveclassmethodasync

noclick.PhoneNumberOrder.retrieve(
    order_id, *, user=..., client=None
) -> PhoneNumberOrder

Load an order by id now; an unknown id raises noclick.NotFound.

Parameters
order_idstrrequired
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
PhoneNumberOrder

The order, as it stands.

Usage
order = noclick.PhoneNumberOrder.retrieve("5f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f")
print(order.status, order.reason)

resubmitasync

phone_number_order.resubmit(*, address=None, requirements=None) -> PhoneNumberOrder

File 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.

Parameters
addressNumberHolder | Nonedefault None
requirementsMapping[str, str | NumberDocument] | Nonedefault None
Returns
PhoneNumberOrder

This order, in_review again.

Usage
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() -> PhoneNumber

The number, once the order completed; noclick.Conflict while it hasn't (refresh() first).

Returns
PhoneNumber

The noclick.PhoneNumber the order became.

Usage
order.refresh()
if order.status == "completed":
    number = order.phone_number()
    number.answer_with(noclick.Agent.from_name("receptionist"))