Dict

Dicts: a key-value store for small state that code and agents share.

seen = noclick.Dict.from_name("processed-orders", create_if_missing=True)
if "o_1841" not in seen:
    seen["o_1841"] = {"status": "refunded"}
print(seen.increment("refunds-today"))

Keys are strings and values are JSON (a dict, list, string, number, bool or None), never pickled, so a value your Python code writes reads the same from TypeScript, an agent or the console. A dict works like Python's own: d[key], d[key] = value, key in d, del d[key], len(d) and iterating its keys, plus expiry (ttl_s), locks (put_if_absent) and atomic counters (increment). Each of those reads or writes the API, so the .aio forms of get, put and pop are how async code does the same.

class Dict

A key-value store: string keys, JSON values, optional expiry per key.

Read and write it like a Python dict (d[key], d[key] = value, key in d, del d[key], len(d), for key in d); a missing key raises KeyError there and get returns a default instead. Each of those is a call to the API, never a local copy, so two processes see each other's writes at once. A dict object is always truthy, so if seen: never makes a call. key_count and size_bytes are as of the last load; len(d) reads the count fresh.

Attributes
idstr
namestr
key_countint | None

How many keys it holds (expired keys are gone).

size_bytesint | None

The size of its keys and values as JSON.

used_bylist[dict[str, Any]]

The apps that bind it: kind (project), id, name and the binding name (apps are in invite-only beta, so it's empty).

regionstr | None

The region it keeps its data in (noclick.regions()), fixed when it was created.

user_idstr | None
created_atAny
console_urlstr | None

This dict in the developer console, with its key browser (None off the hosted API).

createclassmethodasync

noclick.Dict.create(name, *, region=None, user=..., client=None) -> Dict

Create a dict called name (1-64 letters, digits and . _ -), unique to its user (a taken name raises noclick.Conflict with existing_id). region (noclick.regions(kind="dict")) is where its keys are kept, for good; without one, your account's default region, else us-east. Dicts are kept in us-east only for now: another region, or a default region elsewhere, raises noclick.InvalidRequest naming it.

Parameters
namestrrequired
regionstr | 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
Dict

The new dict, empty.

Usage

A dict of the account's own:

seen = noclick.Dict.create("processed-orders")

Created inside as_user, it belongs to (and bills) that user:

with noclick.as_user("acme"):
    flags = noclick.Dict.create("feature-flags")

from_nameclassmethod

noclick.Dict.from_name(
    name, *, create_if_missing=False, region=None, user=..., client=None
) -> Dict

A lazy reference to the dict called name in the current (or given) user. With create_if_missing=True it's created the first time it's used, in region (as create).

Inside a deployed app (signed in by its deployment) the name is looked up in the app's own namespace for the environment the code runs in, and one made there is the app's: a name bound to the app by hand wins, and in a preview of an app whose preview_resources is branch the branch gets its own copy of the keys the first time it uses it, so a preview never changes production's data.

Parameters
namestrrequired
create_if_missingbooldefault False
regionstr | 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
Dict

A dict that loads (or is created) on first use. Nothing is sent yet.

Usage

Make your code the definition, so the first run creates it:

seen = noclick.Dict.from_name("processed-orders", create_if_missing=True)
if "o_1841" not in seen:
    seen["o_1841"] = {"status": "refunded"}

A user's own, for per-customer settings:

with noclick.as_user("acme"):
    flags = noclick.Dict.from_name("feature-flags", create_if_missing=True)
    print(flags.get("new-checkout", default=False))

from_idclassmethod

noclick.Dict.from_id(dict_id, *, client=None, user=...) -> Dict

A lazy reference to a dict by id. There's no Dict.get(id), since get reads a key: call hydrate() on this to load it now.

Parameters
dict_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
Dict

A dict that loads the first time you use it. Nothing is sent yet.

Usage
seen = noclick.Dict.from_id("5b8e2c71-4d9a-4f36-a0e2-7c1b9d3f6a58").hydrate()
print(seen.name, seen.key_count)

listclassmethodasync

noclick.Dict.list(*, limit=None, user_filter=None, user=..., client=None) -> list[Dict]

The current user's dicts (the account's own outside 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[Dict]

Every matching dict, newest first, fetched page by page.

Usage

Your account's dicts:

for store in noclick.Dict.list():
    print(store.name, store.key_count, store.size_bytes)

Every user's:

every = noclick.Dict.list(user_filter=noclick.ALL_USERS)

list_pageclassmethodasync

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

One page of dicts; 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 dicts and whose next_cursor is None on the last page.

Usage
page = noclick.Dict.list_page(limit=50, user_filter=noclick.ALL_USERS)
for store in page.data:
    print(store.user_id, store.name, store.key_count)

__getitem__

d[key] -> Any

Read a key; a missing (or expired) one raises KeyError.

Parameters
keystrrequired
Returns
Any

The value, as the JSON it was stored as.

Usage
flags = noclick.Dict.from_name("feature-flags")
if flags["new-checkout"]:
    print("New checkout is on")

getasync

d.get(key, default=None) -> Any

Read a key, or default when it's missing or expired.

Parameters
keystrrequired
defaultAnydefault None
Returns
Any

The value as the JSON it was stored as, or default.

Usage
flags = noclick.Dict.from_name("feature-flags")
if flags.get("new-checkout", default=False):
    print("New checkout is on")

keysasync

d.keys(prefix=None, *, limit=None) -> list[str]

The keys, in order, or those that start with prefix.

Parameters
prefixstr | Nonedefault None
limitint | None

Return at most this many.

(default None)
Returns
list[str]

Every matching key (or the first limit), fetched page by page.

Usage
seen = noclick.Dict.from_name("processed-orders")
for key in seen.keys(prefix="lock:"):
    print(key)

itemsasync

d.items(prefix=None, *, limit=None) -> list[DictItem]

The keys and their values, in key order, or those whose key starts with prefix.

Parameters
prefixstr | Nonedefault None
limitint | None

Return at most this many.

(default None)
Returns
list[DictItem]

A DictItem per key (or for the first limit): it unpacks as key, value and also carries expires_at, updated_at and size_bytes.

Usage

Read them like a Python dict's items:

seen = noclick.Dict.from_name("processed-orders")
for key, value in seen.items(prefix="o_", limit=100):
    print(key, value)

See what's about to expire:

seen = noclick.Dict.from_name("processed-orders")
for item in seen.items(prefix="lock:"):
    print(item.key, item.expires_at)

items_pageasync

d.items_page(prefix=None, *, cursor=None, limit=None) -> Page

One page of items in key order; pass next_cursor back as cursor.

Parameters
prefixstr | Nonedefault None
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)
Returns
Page

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

Usage
seen = noclick.Dict.from_name("processed-orders")
page = seen.items_page(prefix="o_", limit=50)
for key, value in page.data:
    print(key, value)

putasync

d.put(key, value, *, ttl_s=None) -> None

Write a key. With ttl_s it expires that many seconds from now; without, it stays until it's removed (and loses any expiry it had). d[key] = value does the same with no expiry.

Parameters
keystrrequired
valueAnyrequired
ttl_sint | Nonedefault None
Usage

Remember an order you've handled:

seen = noclick.Dict.from_name("processed-orders", create_if_missing=True)
seen.put("o_1841", {"status": "refunded", "by": "support-bot"})

A cache entry for a day:

cache = noclick.Dict.from_name("rates-cache", create_if_missing=True)
cache.put("usd:eur", 0.92, ttl_s=86400)

put_if_absentasync

d.put_if_absent(key, value, *, ttl_s=None) -> bool

Write a key only if it isn't there (an expired key counts as absent), in one atomic step: of any number of callers racing, exactly one wins. That makes it a lock (with ttl_s, so a crashed holder's lock frees itself) or a check that something runs once.

Parameters
keystrrequired
valueAnyrequired
ttl_sint | Nonedefault None
Returns
bool

True if this call wrote it, False if the key was already there.

Usage

Only one worker runs the nightly job; the lock frees itself after five minutes:

locks = noclick.Dict.from_name("locks", create_if_missing=True)
if locks.put_if_absent("nightly-report", "worker-3", ttl_s=300):
    print("This worker runs it")

Handle each webhook delivery once, however often it's retried:

seen = noclick.Dict.from_name("processed-orders", create_if_missing=True)
if seen.put_if_absent("evt_9f2c", True, ttl_s=7 * 86400):
    print("First time: handle it")

incrementasync

d.increment(key, by=1, *, ttl_s=None) -> int

Add by (which may be negative) to a counter, atomically: concurrent increments never lose a count. A missing key starts at 0; ttl_s sets its expiry only when this call creates it, so a counter per window (visits:2026-09-28) cleans itself up. A key holding anything but an integer raises noclick.InvalidRequest.

Parameters
keystrrequired
byintdefault 1
ttl_sint | Nonedefault None
Returns
int

The counter's value after this increment.

Usage

Count, and read the total in the same call:

stats = noclick.Dict.from_name("stats", create_if_missing=True)
total = stats.increment("refunds-today")

A rate limit: at most 100 calls per customer per hour:

limits = noclick.Dict.from_name("rate-limits", create_if_missing=True)
if limits.increment("calls:acme:2026-09-28T14", ttl_s=3600) > 100:
    print("Over the limit; try again next hour")

updateasync

d.update(items, *, ttl_s=None) -> None

Write up to 1,000 keys in one call, all of them or none; ttl_s applies to each.

Parameters
itemsMapping[str, Any]required
ttl_sint | Nonedefault None
Usage
flags = noclick.Dict.from_name("feature-flags", create_if_missing=True)
flags.update({"new-checkout": True, "beta-search": False, "max-upload-mb": 50})

popasync

d.pop(key, default=...) -> Any

Remove a key and return its value. A missing key returns default, or raises KeyError when you gave none, like a Python dict's pop.

Parameters
keystrrequired
defaultAnydefault ...
Returns
Any

The value the key held, or default.

Usage
jobs = noclick.Dict.from_name("pending-exports", create_if_missing=True)
job = jobs.pop("acme", None)
if job is not None:
    print("Exporting", job)

removeasync

d.remove(key) -> bool

Remove a key if it's there. del d[key] does the same and raises KeyError on a missing key; this is its form that doesn't, with an .aio for async code.

Parameters
keystrrequired
Returns
bool

Whether the key was there to remove.

Usage
locks = noclick.Dict.from_name("locks")
locks.remove("nightly-report")   # done: let the next run take it

clearasync

d.clear() -> Operation

Remove every key. The dict itself stays, empty.

Returns
Operation

The clear's operation: undo() puts back every key it removed that nothing has written since.

Usage
cache = noclick.Dict.from_name("rates-cache")
cleared = cache.clear()
cleared.undo()   # the keys are back

historyasync

d.history(key=None, *, limit=None) -> list[DictChange]

Its writes of the last 30 days, newest first, each with the value it replaced; key keeps one key's.

Parameters
keystr | Nonedefault None
limitint | None

Return at most this many.

(default None)
Returns
list[DictChange]

Each change.

Usage
prefs = noclick.Dict.from_name("prefs")
for change in prefs.history("theme"):
    print(change.at, change.op, change.previous)

restore_changeasync

d.restore_change(change) -> None

Put the key back as change found it: the value it replaced, or gone, when the change created the key. A value that has expired since raises noclick.Conflict.

Parameters
changeAnyrequired
Usage

Undo a bad write:

prefs = noclick.Dict.from_name("prefs")
prefs["theme"] = "neon"
prefs.restore_change(prefs.history("theme")[0])

exportasync

d.export() -> Operation

Export every key as it stands at one moment, as JSON Lines: one {"key", "value", "expires_at"} per line, in key order.

Returns
Operation

An operation whose wait() gives an ExportBundle: a zip at url holding the file.

Usage
seen = noclick.Dict.from_name("processed-orders")
bundle = seen.export().wait()
print(bundle.url)

deleteasync

d.delete() -> TrashItem

Delete the dict and every key in it: it goes to the trash, restorable for 30 days. A dict an app still binds raises noclick.Conflict until you unbind it.

Returns
TrashItem

Its TrashItem: restore() puts it back, keys and all.

Usage
cache = noclick.Dict.from_name("rates-cache")
item = cache.delete()
item.restore()   # it's back

DictItem

class DictItem

One key of a dict as items() reads it: its key and value, when it expires_at (None if it doesn't), when it was last written (updated_at) and size_bytes, the size of its value as JSON. It unpacks like a pair, so for key, value in d.items() and dict(d.items()) work as they do on a Python dict. A listing from keys() carries no values.

Attributes
keystr
valueAny
expires_atAny
updated_atAny
size_bytesint | None
rawdict[str, Any]

The response JSON exactly as the API sent it.

DictChange

class DictChange

One write to a dict, from its history: op is created (the key was new), changed or deleted, and previous the value it replaced (None for a new key), which expired at previous_expires_at. A key changed again within a minute adds none, so a busy key keeps one a minute.

Attributes
idstr
keystr
opstr
previousAny
previous_expires_atAny
atAny
rawdict[str, Any]

The response JSON exactly as the API sent it.