User

Users: the people and companies your product acts for, each known by your own id.

class User

One of your users, a person or a company, known by your own id for it. User.from_id("user_42", create_if_missing=True) is lazy: nothing is sent until the user is used.

Attributes
idstr

Your id for the user.

namestr | None
emailstr | None
phonestr | None
phone_verified_atAny

When the phone was proven (None if never).

spend_cap_usd_per_monthfloat | None
metadatadict[str, Any] | None
timezonestr | None

Their IANA timezone, like Europe/Lisbon: agents acting for them tell the time in it, and Cron(tz="user") fires in it. None: UTC.

statusstr
created_atAny
spendingSpending | None

Whether they can spend right now: paused or not, what NoClick tells them, and how many of their messages wait (see pause_spending).

console_urlstr | None

This user in the developer console (None off the hosted API).

humanHuman

The person behind this user: user.human.approve("Send it?").wait().

createclassmethodasync

noclick.User.create(
    id, *, name=None, email=None, phone=None, spend_cap_usd_per_month=None,
    metadata=None, timezone=None, idempotency_key=None, client=None
) -> User

Create a user, keyed by your own id for it: 1 to 255 printable ASCII characters, without spaces or /, other than all and none. An id another live user has raises noclick.Conflict (its existing_id is that id). timezone is where they are, as an IANA name ("America/New_York"): every turn of an agent acting for them starts with the date and time there, unless the agent has a timezone of its own.

Parameters
idstrrequired
namestr | Nonedefault None
emailstr | Nonedefault None
phonestr | Nonedefault None
spend_cap_usd_per_monthfloat | Nonedefault None
metadatadict[str, Any] | Nonedefault None
timezonestr | Nonedefault None
idempotency_keystr | None

Makes a retried create safe: the same key returns the first result. Generated when omitted.

(default None)
clientClient | None

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

(default None)
Returns
User

The new user, loaded.

Usage

Create one when a user signs up, keyed by your own id for them:

user = noclick.User.create("user_42", name="Ana Lima", email="ana@example.com")
print(user.id)   # user_42

If the user may already exist, the conflict names it:

try:
    user = noclick.User.create("user_42")
except noclick.Conflict as exc:
    user = noclick.User.get(exc.existing_id)

Say where they are, so their agents know what "tomorrow morning" means:

user = noclick.User.create("user_42", email="ana@example.com", timezone="America/Sao_Paulo")

from_idclassmethod

noclick.User.from_id(
    id, *, create_if_missing=False, name=..., email=..., phone=...,
    spend_cap_usd_per_month=..., metadata=..., timezone=..., client=None
) -> User

A lazy reference to the user with your id. With create_if_missing=True your code is the definition: it's created with the given fields on first use, and an existing user whose name, email, phone, spend_cap_usd_per_month, metadata or timezone differ is updated to match. A field you leave out is never changed, and None clears one, as in update().

Parameters
idstrrequired
create_if_missingbooldefault False
nameAnyomit to leave it unchanged
emailAnyomit to leave it unchanged
phoneAnyomit to leave it unchanged
spend_cap_usd_per_monthAnyomit to leave it unchanged
metadataAnyomit to leave it unchanged
timezoneAnyomit to leave it unchanged
clientClient | None

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

(default None)
Returns
User

A user that loads, or is created, the first time it's used. Nothing is sent yet.

Usage

Refer to a user by your id without a request:

user = noclick.User.from_id("user_42")
print(user.status)   # loads it now; noclick.NotFound if there's none

Or create it the first time it's used, so sign-up needs no extra call. Two requests that race both get the same user:

user = noclick.User.from_id("user_42", create_if_missing=True, email="ana@example.com")
with noclick.as_user(user):
    agent = noclick.Agent.from_name("assistant")

Run at every sign-in, it keeps the user's details in step with yours:

user = noclick.User.from_id("user_42", create_if_missing=True, name="Ana Lima", email="ana@example.com")
print(user.email)   # ana@example.com, even if it was another address before

Pass the timezone your product already knows:

user = noclick.User.from_id("user_42", create_if_missing=True, timezone="Europe/Lisbon")
print(user.timezone)

getclassmethodasync

noclick.User.get(id, *, client=None) -> User

Load a user by your id now; an unknown id raises noclick.NotFound.

Parameters
idstrrequired
clientClient | None

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

(default None)
Returns
User

The user, loaded.

Usage
user = noclick.User.get("user_42")
print(user.name, user.status)

listclassmethodasync

noclick.User.list(
    *, q=None, status=None, spending=None, limit=None, client=None
) -> list[User]

Your users, newest first (or the first limit). q is a case-insensitive prefix of the id, name or email; status active or suspended; spending "paused" or "active", to find the users whose spending you've paused (say, to check them against your billing after an outage).

Parameters
qstr | Nonedefault None
statusstr | Nonedefault None
spendingstr | Nonedefault None
limitint | None

Return at most this many.

(default None)
clientClient | None

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

(default None)
Returns
list[User]

Every matching user, fetched page by page.

Usage
for user in noclick.User.list(q="ana"):
    print(user.id, user.email)

suspended = noclick.User.list(status="suspended", limit=20)
paused = noclick.User.list(spending="paused")

list_pageclassmethodasync

noclick.User.list_page(
    *, cursor=None, limit=None, q=None, status=None, spending=None, client=None
) -> Page

One page of users; pass next_cursor back as cursor. Takes list's filters.

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)
qstr | Nonedefault None
statusstr | Nonedefault None
spendingstr | Nonedefault None
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 users and whose next_cursor is None on the last page.

Usage
page = noclick.User.list_page(limit=50)
while True:
    for user in page.data:
        print(user.id)
    if page.next_cursor is None:
        break
    page = noclick.User.list_page(limit=50, cursor=page.next_cursor)

updateasync

user.update(
    *, id=..., name=..., email=..., phone=..., spend_cap_usd_per_month=...,
    metadata=..., timezone=...
) -> User

Change fields (None clears one); a field you leave out is unchanged. A new id renames the user: everything it owns follows, and an id another user has raises noclick.Conflict. A user acting for themselves may change their own timezone, their session included (it may change nothing else).

Parameters
idAnyomit to leave it unchanged
nameAnyomit to leave it unchanged
emailAnyomit to leave it unchanged
phoneAnyomit to leave it unchanged
spend_cap_usd_per_monthAnyomit to leave it unchanged
metadataAnyomit to leave it unchanged
timezoneAnyomit to leave it unchanged
Returns
User

This user, updated.

Usage

Change a user's details:

user = noclick.User.from_id("user_42")
user.update(name="Ana Lima-Souza", phone=None, spend_cap_usd_per_month=50)

Move a user to a new id, say when your own ids change:

user = noclick.User.from_id("user_42")
user.update(id="acct_9f3a")
print(user.id)   # acct_9f3a

They moved: their agents' next turns and Cron(tz="user") schedules follow:

user = noclick.User.from_id("user_42")
user.update(timezone="Asia/Tokyo")

suspendasync

user.suspend() -> User

Stop all activity for this user: every call made for it then fails with noclick.PermissionDenied, and its history stays readable.

Returns
User

This user, now suspended.

Usage
user = noclick.User.from_id("user_42")
user.suspend()
print(user.status)   # suspended

pause_spendingasync

user.pause_spending(*, message=None, action=None) -> User

Stop everything that costs money for this user until resume_spending(), and say what they can do about it. Call it from your billing when they run out (say, at a balance alert) or fall behind. message is what NoClick tells them, in your words, up to 500 characters; action is a (label, url) pair, your checkout or billing page, up to 40 characters and an https URL. Both may use {{user.id}}, {{user.name}}, {{user.email}} and {{user.metadata.<key>}}, filled in now; a field this user lacks raises noclick.InvalidRequest. Without message, the default you set in the console (Settings, Paused spending) is used, and with no default either it raises noclick.InvalidRequest; without action, the default's button.

While paused, a call your code makes for them raises noclick.SpendingPaused; a message they send an agent (an email to its inbox, a post in their channel) is answered once per conversation with message and action and held, up to 24 hours, to run when you resume; a call to a number they reach hears message; schedules, webhooks and app events for their agents are skipped. Work under way finishes the step it's on. What they already have keeps running and billing: pausing isn't cancelling. Reads still work. Pausing a paused user changes its message and action and keeps since and what's held. Only a key acting for your whole account can pause or resume: never the user's own session.

Parameters
messagestr | Nonedefault None
actiontuple[str, str] | Nonedefault None
Returns
User

This user, with spending paused.

Usage

Pause a user when their balance runs low, with your top-up page:

user = noclick.User.from_id("user_42")
user.pause_spending(
    message="You've used your credits.",
    action=("Top up", "https://acme.com/billing/top-up?user={{user.id}}"),
)
print(user.spending.paused, user.spending.since)

Or with the message you set in the console:

noclick.User.from_id("user_42").pause_spending()

resume_spendingasync

user.resume_spending() -> User

Let this user spend again, say once your billing has their payment. What was held for them runs now, in the order it arrived: each message as the turn it would have been, with when it arrived, so the agent knows it waited. Resuming a user who isn't paused changes nothing.

Returns
User

This user, with spending no longer paused.

Usage
user = noclick.User.from_id("user_42")
held = user.spending.held
user.resume_spending()
print(f"Resumed; {held} held messages are running now")

deleteasync

user.delete(*, export=False) -> Operation | None

Mark the user deleted, which frees its id for reuse. Nothing acts for them afterwards (invite-only beta): their connections' grants are revoked at each provider, their agents' triggers stop, their inboxes stop receiving mail and their phone numbers are released. What's kept is the record: operations, tool calls, conversations, usage and spend stay readable with user_filter, and their databases, buckets, repos and files stay until you delete them. export=True packs up everything the user holds first, as export() does, and deletes the user once the export is stored; nothing is deleted if the export fails or is canceled. Only a call acting for the account deletes users.

Parameters
exportbooldefault False
Returns
Operation | None

None; with export=True, an operation whose .wait() gives the ExportBundle once the user is deleted.

Usage

Delete a user:

noclick.User.from_id("user_42").delete()

Or hand the user their data, then delete them:

user = noclick.User.from_id("user_42")
bundle = user.delete(export=True).wait()
print(bundle.url, bundle.expires_at)

exportasync

user.export() -> Operation

Pack up everything the user holds, in forms that work without NoClick: their own agents' definitions as JSON, their conversations with any agent as JSON Lines transcripts, what agents remember about them as Markdown, their databases as SQL, their buckets' and volumes' files, their dicts and queues as JSON Lines, their repos as git bundles, their apps' settings, connections and secrets by name only (never a token or a value), and each phone number and domain with the call that moves it (transfer_out). The user keeps working; one export of a user runs at a time (another raises noclick.Conflict naming it).

Returns
Operation

An operation whose .wait() gives the ExportBundle: a zip url valid until expires_at (7 days), its contents, and remaining_dependencies, what still runs on NoClick.

Usage
user = noclick.User.from_id("user_42")
bundle = user.export().wait()
print(bundle.url, bundle.expires_at)
for item in bundle.contents:
    print(item.kind, item.name, item.detail)
print("Still on NoClick:", ", ".join(bundle.remaining_dependencies))

verify_phoneasync

user.verify_phone(*, phone=None) -> PhoneVerification

Text a verification code to the user's phone (or phone=, which becomes its number); confirm it with check_phone(code).

Parameters
phonestr | Nonedefault None
Returns
PhoneVerification

The number the code went to, and when the code expires.

Usage
user = noclick.User.from_id("user_42")
sent = user.verify_phone(phone="+14155550100")
print(sent.phone, sent.expires_at)

check_phoneasync

user.check_phone(code) -> bool

Check the code the user received. A wrong code raises InvalidRequest, an expired one Expired, and too many tries RateLimited.

Parameters
codestrrequired
Returns
bool

True once the phone is verified; phone_verified_at is then set.

Usage
user = noclick.User.from_id("user_42")
try:
    user.check_phone("482913")
    print(user.phone_verified_at)
except noclick.InvalidRequest as err:
    print(err.message)   # That code isn't right.

sessionasync

user.session(scopes, *, ttl_s=None) -> Session

Mint a short-lived token that acts only as this user, with scopes (a subset of your key's: agents, humans, credentials:request, credentials:use, channels, inbox, ai). ttl_s is at most 24h. Hand session.token to your frontend, or use noclick.Client.for_session(session). Revoking the minting key revokes its sessions.

Parameters
scopesstr | Sequence[str]required
ttl_sint | float | timedelta | Nonedefault None
Returns
Session

The session: its token, the scopes it carries and when it expires.

Usage

Mint one per page load on your server and hand the token to the browser:

user = noclick.User.from_id("user_42")
session = user.session(["agents", "humans"], ttl_s=3600)
token = session.token   # for the browser; it acts only as this user

Or act as the user from your own code, with only those scopes:

client = noclick.Client.for_session(session)
agent = noclick.Agent.from_name("assistant", client=client)

PhoneVerification

class PhoneVerification

A verification code on its way to the user's phone.

Attributes
phonestr | None
statusstr | None
expires_atAny
rawdict[str, Any]

The response JSON exactly as the API sent it.

Spending

class Spending

Whether a user can spend right now, as user.spending reads it. A paused user can't start anything that costs money until you call resume_spending(); message and action (a (label, url) pair) are what NoClick tells them meanwhile, since is when you paused them, held how many of their messages wait to run when you resume, and paused_by the key that paused them ("console" from the console).

Attributes
pausedbool
messagestr | None
actiontuple[str, str] | None
sinceAny
heldint
paused_bystr | None
rawdict[str, Any]

The response JSON exactly as the API sent it.