User
Users: the people and companies your product acts for, each known by your own id.
class UserOne 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.
- Like every live object, it also has hydrate(), refresh() and raw.
| id | str | Your id for the user. |
| name | str | None | |
| str | None | ||
| phone | str | None | |
| phone_verified_at | Any | When the phone was proven (None if never). |
| spend_cap_usd_per_month | float | None | |
| metadata | dict[str, Any] | None | |
| timezone | str | None | Their IANA timezone, like |
| status | str | |
| created_at | Any | |
| spending | Spending | None | Whether they can spend right now: paused or not, what NoClick tells them, and how many of their messages wait (see |
| console_url | str | None | This user in the developer console (None off the hosted API). |
| human | Human | The person behind this user: |
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
) -> UserCreate 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.
NoneNoneNoneNoneNoneNoneMakes a retried create safe: the same key returns the first result. Generated when omitted.
None)The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The new user, loaded.
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_42If 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
) -> UserA 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().
FalseThe client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A user that loads, or is created, the first time it's used. Nothing is sent yet.
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 noneOr 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 beforePass 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) -> UserLoad a user by your id now; an unknown id raises noclick.NotFound.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The user, loaded.
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).
NoneNoneNoneReturn at most this many.
None)The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching user, fetched page by page.
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
) -> PageOne page of users; pass next_cursor back as cursor. Takes list's filters.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)NoneNoneNoneThe client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A page whose data holds the users and whose next_cursor is None on the last page.
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=...
) -> UserChange 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).
This user, updated.
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_9f3aThey 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() -> UserStop all activity for this user: every call made for it then fails
with noclick.PermissionDenied, and its history stays readable.
This user, now suspended.
user = noclick.User.from_id("user_42")
user.suspend()
print(user.status) # suspendedpause_spendingasync
user.pause_spending(*, message=None, action=None) -> UserStop 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.
NoneNoneThis user, with spending paused.
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() -> UserLet 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.
This user, with spending no longer paused.
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 | NoneMark 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.
FalseNone; with export=True, an operation whose .wait() gives the
ExportBundle once the user is deleted.
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() -> OperationPack 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).
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.
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) -> PhoneVerificationText a verification code to the user's phone (or phone=, which
becomes its number); confirm it with check_phone(code).
NoneThe number the code went to, and when the code expires.
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) -> boolCheck the code the user received. A wrong code raises
InvalidRequest, an expired one Expired, and too many tries
RateLimited.
True once the phone is verified; phone_verified_at is then set.
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) -> SessionMint 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.
NoneThe session: its token, the scopes it carries and when it expires.
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 userOr 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 PhoneVerificationA verification code on its way to the user's phone.
| phone | str | None | |
| status | str | None | |
| expires_at | Any | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
Spending
class SpendingWhether 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).
| paused | bool | |
| message | str | None | |
| action | tuple[str, str] | None | |
| since | Any | |
| held | int | |
| paused_by | str | None | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |