Connection

Connections: named links to your users' accounts on other services, and what those services can do.

gmail = noclick.Connection.from_name("gmail", integration="gmail", create_if_missing=True)
if not gmail.connected:
    send_to_user(gmail.connect_url)
gmail.wait()
gmail.run("send_email_message", {...})

A connection is a name in one scope (a user, or your account's own). Code, toolkits and agents name it, so they always act in exactly that account; two accounts on one service are two connections. Reconnecting keeps the name.

An operation that needs no account (exa's web search) needs no connection either: noclick.run("exa.search", {...}) and tools=["exa.search"] run it on NoClick's key, for every user. A connection named exa holding your own key takes over the same refs. Connection.integrations() finds a service, and operations() says what a connection's service can do.

class Connection

A named connection to an account on another service. Secrets never come back; token() mints an OAuth access token when the key has credentials:token.

status is pending until someone connects an account at connect_url, connected after, and disconnected once the account is revoked (connect_link() mints a link to reconnect it). Operations that need no account run on NoClick's key even while it's pending.

The accounts connected in the NoClick app are connections too, in your account's own scope: origin is "app" and the id is credential:<id> (their name is the app's, so refer to them by id). They run, look up, verify and take require_approval like any connection, and an account agent takes one as a tool (the Connection, or "credential:<id>", "credential:<id>.operation"; a user's agent can't); the app connects, reconnects and removes them. One shared with your account has access "view" or "edit": it runs, but its token and approval rules stay its owner's.

An app's preview only reads through a connection: an operation that writes in its account is refused (noclick.PermissionDenied) unless the connection is a test account (mark_test_account()).

Attributes
idstr
namestr
integrationstr
credential_typestr | None
user_idstr | None
statusstr
originstr

api for a named connection, app for an account connected in the app.

accessstr

Your account's access: owner, or edit/view on one shared with it.

approval_operationslist[str]

Operations its owner approves call by call wherever it runs (*: all of them); require_approval adds to them.

accountstr | None

The connected account as the provider named it, when proven.

test_accountbool

Whether it's a test account, which an app's preview writes through as well as reads.

used_bylist[dict[str, Any]]

What holds it: agents (in their tools, as their model_connection, or given it by their user), hosted MCP servers with it in their tools, and apps that bind it. Each is kind (agent, mcp_server or project), id, name, user_id and an app's binding name.

connected_atAny
created_atAny
updated_atAny
connectedbool
connect_urlstr | None

The open link where someone connects (or reconnects) the account, if one is open.

verificationVerification | None
console_urlstr | None

This connection's kind in the developer console (None off the hosted API).

from_nameclassmethod

noclick.Connection.from_name(
    name, *, integration=None, create_if_missing=False, credential_type=None,
    data=None, redirect_url=None, reason=None, send_to=None, user=..., client=None
) -> Connection

A lazy reference to the connection called name in the current (or given) user. With create_if_missing=True it's created on first use, for integration (a name, or an IntegrationInfo): holding data when given (a key you hold), else pending with a connect link (the link options are create's). An existing connection is used as it is. operations(), operation() and events() read integration's catalog without loading or creating it.

Parameters
namestrrequired
integrationAnydefault None
create_if_missingbooldefault False
credential_typestr | Nonedefault None
datadict[str, Any] | Nonedefault None
redirect_urlstr | Nonedefault None
reasonstr | Nonedefault None
send_toAnydefault 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
Connection

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

Usage

Refer to a connection that already exists:

gmail = noclick.Connection.from_name("gmail", user="user_42")
print(gmail.status)   # loads it now; noclick.NotFound if there's none

Or create it the first time it's used, pending with a link for the user. The same line works for a new user and a returning one:

with noclick.as_user("user_42"):
    gmail = noclick.Connection.from_name(
        "gmail", integration="gmail", create_if_missing=True,
        reason="to send your weekly summaries",
    )
    print(gmail.status, gmail.connect_url)   # pending https://noclick.com/credential/provide/...

With a key you already hold, it's created holding that key (and never overwrites one):

crm = noclick.Connection.from_name(
    "crm", integration="affinity", data={"api_key": "..."},
    create_if_missing=True, user="user_42",
)

from_idclassmethod

noclick.Connection.from_id(connection_id, *, client=None, user=...) -> Connection

A lazy reference to a connection by NoClick id, in the current (or given) user; credential:<id> for an account connected in the app.

Parameters
connection_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
Connection

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

Usage
connection = noclick.Connection.from_id("3f1d2c4b-8a7e-4f6d-9b2a-1c5e7d9f0a31", user="user_42")
print(connection.name, connection.status)

slack = noclick.Connection.from_id("credential:9b2a1c5e-7d9f-4a31-8f6d-3f1d2c4b8a7e")
slack.run("send_message", {"channel": "#ops", "text": "Deployed."})

createclassmethodasync

noclick.Connection.create(
    name, *, integration=None, credential_type=None, data=None, redirect_url=None,
    reason=None, send_to=None, user=..., client=None
) -> Connection

Create a connection called name (unique per user; a duplicate raises noclick.Conflict with existing_id). With data it holds that credential at once (validated and probed first); without, it's pending and connect_url is where someone connects it. send_to ("user", an email, or a dict) also delivers the link.

Parameters
namestrrequired
integrationAnydefault None
credential_typestr | Nonedefault None
datadict[str, Any] | Nonedefault None
redirect_urlstr | Nonedefault None
reasonstr | Nonedefault None
send_toAnydefault 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
Connection

The new connection: pending with its connect_url, or connected (with its verification) when you passed data.

Usage

Create a pending connection and show the user its link:

with noclick.as_user("user_42"):
    slack = noclick.Connection.create("slack", integration="slack", reason="to post your team's standup")
    print(slack.connect_url)

Or create it holding a key you already have. A key the provider rejects raises noclick.InvalidRequest and saves nothing:

crm = noclick.Connection.create("crm", integration="affinity", data={"api_key": "..."}, user="user_42")
print(crm.status)   # connected

A model provider's connection is what an agent on a CLI harness thinks with (its model_connection). Pass a key as api_key; a Claude or ChatGPT plan is signed in only at the link, and only on a user's own connection:

claude = noclick.Connection.create("claude", integration="anthropic", data={"api_key": "sk-ant-..."})
with noclick.as_user("user_42"):
    plan = noclick.Connection.create("claude", integration="anthropic")
    print(plan.connect_url)   # "Paste an Anthropic key" or "Sign in with Claude"

If the name may be taken, the conflict names the connection already there:

try:
    slack = noclick.Connection.create("slack", integration="slack", user="user_42")
except noclick.Conflict as exc:
    slack = noclick.Connection.from_id(exc.existing_id, user="user_42")

getclassmethodasync

noclick.Connection.get(ref, *, user=..., client=None) -> Connection

Load the connection with this id or name now; an unknown one raises noclick.NotFound.

Parameters
refstrrequired
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
Connection

The connection, loaded.

Usage
with noclick.as_user("user_42"):
    gmail = noclick.Connection.get("gmail")
    print(gmail.status, gmail.account)

listclassmethodasync

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

The current user's connections (the account's own outside as_user), or those user_filter covers: named ones newest first, then, in the account's own scope, the accounts connected in the app. origin keeps only "api" or "app" ones.

Parameters
integrationAnydefault None
originstr | Nonedefault 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
list[Connection]

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

Usage
with noclick.as_user("user_42"):
    for connection in noclick.Connection.list():
        print(connection.name, connection.integration, connection.status)

every_gmail = noclick.Connection.list(integration="gmail", user_filter=noclick.ALL_USERS)
from_the_app = noclick.Connection.list(origin="app")

list_pageclassmethodasync

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

One page of connections; 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)
integrationAnydefault None
originstr | Nonedefault 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 connections and whose next_cursor is None on the last page.

Usage
page = noclick.Connection.list_page(limit=50, user_filter=noclick.ALL_USERS)
while True:
    for connection in page.data:
        print(connection.user_id, connection.name, connection.status)
    if page.next_cursor is None:
        break
    page = noclick.Connection.list_page(limit=50, user_filter=noclick.ALL_USERS, cursor=page.next_cursor)

integrationsclassmethodasync

noclick.Connection.integrations(
    query=None, *, kind=None, client=None
) -> list[IntegrationInfo]

The services NoClick can operate and the model providers its agents think with: every one, or those whose name or label contains query (ignoring case). kind="app" keeps the services, whose operations are tools; kind="model" keeps the model providers (anthropic, openai, openrouter, ...), which an agent names as its model_connection. The catalog is your account's, the same for every user.

Parameters
querystr | Nonedefault None
kindstr | Nonedefault None
clientClient | None

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

(default None)
Returns
list[IntegrationInfo]

Each one's name (what integration= takes), label, kind, operation_count, account_free_operation_count (how many run with no account, by the service's name), connect_with (how a person connects an account) and subscription (whether its link also offers signing in with a Claude or ChatGPT plan).

Usage

Find a service, then make a connection to it:

[crm] = noclick.Connection.integrations("affinity")
print(crm.label, crm.connect_with)   # Affinity ['key']
with noclick.as_user("user_42"):
    affinity = noclick.Connection.from_name("crm", integration=crm, create_if_missing=True)
    print(affinity.connect_url)

Or list every service a person connects by signing in:

for service in noclick.Connection.integrations():
    if "oauth" in service.connect_with:
        print(service.name, service.label)

Or the services with operations that need no account, which run by the service's name with no connection (noclick.run("exa.search", ...)):

for service in noclick.Connection.integrations():
    if service.account_free_operation_count:
        print(service.name, service.account_free_operation_count)   # exa 4

Or the model providers an agent's model_connection can name:

for provider in noclick.Connection.integrations(kind="model"):
    print(provider.name, provider.connect_with, provider.subscription)   # anthropic ['key'] True

only

connection.only(*operations) -> ConnectionOperations

A tool ref to some of its operations, for tools= (gmail.only("send_email_message")); a lazy create_if_missing connection is still created first.

Parameters
*operationsstr
Returns
ConnectionOperations

A ref to just those operations of this connection, to list in tools=; bind(...) on it fixes arguments the model mustn't choose.

Usage

A large integration doesn't fit an agent whole, so name the operations it needs:

with noclick.as_user("user_42"):
    stripe = noclick.Connection.from_name("stripe", integration="stripe", create_if_missing=True)
    support = noclick.Agent.from_name(
        "support", create_if_missing=True,
        instructions="You answer the user's billing questions.",
        tools=[stripe.only("list_invoices", "search_charges"), "search"],
    )

checkout

connection.checkout(repository, *, branch=None, path=None, push=None) -> RepoCheckout

One of this connection's repositories (owner/name on its git host; a github connection) checked out in an agent's workspace, for Agent(repos=[...]): cloned into /workspace/<path> (the repository's name when omitted) at a conversation's first turn, on branch (its default when omitted). The agent's git reaches this repository only, through NoClick, with a key made for its sandbox and revoked when the sandbox ends; the connection's own token never enters it. push is the branches it may push to, as globs: NoClick refuses a push to any other ref, and without push the checkout is read-only. gh reads the repository (gh api repos/<owner>/<name>/...) and nothing more, since its writes could move a branch past push: give the agent the connection's operations (github.only("create_pull_request")) for pull requests and issues, which your policy judges. Sends nothing; a lazy create_if_missing connection is created when the agent is saved, and the connection must be connected before a turn can clone.

Parameters
repositorystrrequired
branchstr | Nonedefault None
pathstr | Nonedefault None
pushstr | Sequence[str] | Nonedefault None
Returns
RepoCheckout

A RepoCheckout to list in an agent's repos.

Usage

A docs agent that pushes only to branches it names noclick/...:

github = noclick.Connection.from_name("github", integration="github", create_if_missing=True)
writer = noclick.Agent.from_name(
    "docs-writer",
    create_if_missing=True,
    instructions="You fix the docs issues you're given in /workspace/docs and open a pull request.",
    harness="claude_code",
    model_connection="claude",
    tools=[github.only("create_pull_request")],
    repos=[github.checkout("acme/docs", push="noclick/*")],
)

Read-only, on a branch, in a folder of your choosing:

github = noclick.Connection.from_name("github")
reference = github.checkout("acme/api", branch="release-2.4", path="api")

operationsasync

connection.operations(search=None) -> list[IntegrationOperation]

The operations of this connection's integration: every one, or those whose name or description contains search (ignoring case).

Parameters
searchstr | Nonedefault None
Returns
list[IntegrationOperation]

Each operation's name, description, the JSON schema of its parameters, needs_account (False when it runs on NoClick's key, with no connection or on a pending one) and read_only, a hint from its name rather than a guarantee.

Usage

See what a user's Slack can do before anyone has connected it. With integration= nothing is loaded or created to answer:

with noclick.as_user("user_42"):
    slack = noclick.Connection.from_name("slack", integration="slack", create_if_missing=True)
    for op in slack.operations(search="message"):
        print(op.name, op.read_only, op.description)

Find the operations that need no account. They run by the integration's name with no connection at all (noclick.run("exa.search", ...)), and reading them creates nothing:

exa = noclick.Connection.from_name("exa", integration="exa")
keyless = [op.name for op in exa.operations() if not op.needs_account]

operationasync

connection.operation(name) -> IntegrationOperation

One operation of this connection's integration; an unknown name raises noclick.NotFound.

Parameters
namestrrequired
Returns
IntegrationOperation

The operation, with the JSON schema of the parameters that run takes for it.

Usage

Read an operation's parameters before running it:

slack = noclick.Connection.from_name("slack", integration="slack", user="user_42")
send = slack.operation("send_message_to_channel")
print(send.description, send.parameters["required"])
slack.run("send_message_to_channel", {"channel": "#general", "text": "Standup in 5"})

eventsasync

connection.events() -> list[IntegrationEvent]

The app events this connection's integration can wake an agent with, for noclick.OnEvent. A poll (new mail in Gmail, say) is no event here: only what the app pushes as it happens.

Returns
list[IntegrationEvent]

Every event, with its description and the filters it takes (channel for a Slack message, repository for a GitHub issue).

Usage
slack = noclick.Connection.from_name("slack", integration="slack", user="user_42")
for event in slack.events():
    print(event.name, event.description, list(event.filters))

requestasync

connection.request(*, reason=None, send_to='user', redirect_url=None) -> ConnectLink

Ask the user (or send_to) to connect it: a new connect link, emailed. A connection of your account's own has no user: send it to "owner" (your account's owner) or an email.

Parameters
reasonstr | Nonedefault None
send_toAnydefault 'user'
redirect_urlstr | Nonedefault None
Returns
ConnectLink

The link, already sent: delivered_via says where it went, and .wait() on it returns the connected Connection.

Usage
gmail = noclick.Connection.from_name("gmail", user="user_42")
if gmail.refresh().status == "disconnected":
    link = gmail.request(reason="Gmail stopped working. Reconnect it to keep your summaries coming.")
    print(link.delivered_via)   # ['email']

waitasync

connection.wait(timeout=None) -> Connection

Block until an account is connected (returns at once when it is). It follows the connection, not one link: an account connected through any of its links ends the wait, and a link that lapses after a fresh one was made (connect_link() or request(), from anywhere) hands the wait to the fresh one. Raises noclick.OperationExpired when the open link lapses with no fresh one, or noclick.OperationTimeout after timeout seconds.

Parameters
timeoutfloat | Nonedefault None
Returns
Connection

This connection, connected. After a wait it's reloaded, so account names the account when the provider says who it is.

Usage

Wait in a background job, not while a web request is open:

gmail = noclick.Connection.from_name("gmail", user="user_42")
gmail.wait(timeout=3600)
print(gmail.account)   # ana@example.com

If the link lapses first, send a fresh one and keep waiting:

gmail = noclick.Connection.from_name("gmail", user="user_42")
try:
    gmail.wait()
except noclick.OperationExpired:
    gmail.request(reason="to send your weekly summaries")
    gmail.wait()

import_async

connection.import_(data, *, credential_type=None) -> Connection

Replace the connection's credential with one you hold (a rotated key), validated and probed first. credential_type, when the integration takes more than one, is one of its credential_types (Connection.integrations()).

Parameters
datadict[str, Any]required
credential_typestr | Nonedefault None
Returns
Connection

This connection, holding the new key, with the probe's verification. The key it replaced is deleted.

Usage

Rotate a key:

crm = noclick.Connection.from_name("crm", user="user_42")
crm.import_({"api_key": "..."})
print(crm.status, crm.verification)

Or bring your own key for a service whose operations need no account: a connection named after it runs its refs ("exa.search") on your key instead of NoClick's:

exa = noclick.Connection.from_name("exa", integration="exa", create_if_missing=True)
exa.import_({"api_key": "..."})
print(exa.verification)

runasync

connection.run(operation, arguments=None, *, approval_id=None) -> Any

Run one of the integration's operations in this account. An operation that needs no account (needs_account False in operations()) runs on NoClick's key while the connection is pending, and needs no connection at all through noclick.run; any other raises noclick.NeedsConnection until it's connected and noclick.CredentialDisconnected once revoked (both carry connect_url). A connection holding your own key runs every operation on it. Policy may raise noclick.PolicyDenied or noclick.ApprovalRequired (retry with its approval_id= once .operation.wait() is True). When the service refuses the call, it raises noclick.UpstreamError in the service's own words, with its HTTP status in upstream_status.

Parameters
operationstrrequired
argumentsdict[str, Any] | Nonedefault None
approval_idstr | Nonedefault None
Returns
Any

The operation's data, as plain data (what the service answered).

Usage

Run an operation in the user's account:

gmail = noclick.Connection.from_name("gmail", user="user_42")
gmail.run("send_email_message", {
    "to": ["ana@example.com"],
    "subject": "Your weekly summary",
    "body": "Three invoices were paid this week.",
})

A pending connection runs the operations that need no account at once, on NoClick's key:

exa = noclick.Connection.from_name("exa", integration="exa", create_if_missing=True)
results = exa.run("search", {"query": "invoice automation for small businesses"})

If nothing is connected yet, send the user the link and retry once they've connected:

gmail = noclick.Connection.from_name("gmail", user="user_42")
args = {"to": ["ana@example.com"], "subject": "Your weekly summary", "body": "..."}
try:
    gmail.run("send_email_message", args)
except noclick.NeedsConnection as err:
    notify_user(err.connect_url)
    gmail.wait()
    gmail.run("send_email_message", args)

When your policy holds the call for a person's approval, wait for it and repeat the identical call with the approval's id:

gmail = noclick.Connection.from_name("gmail", user="user_42")
args = {"to": ["ana@example.com"], "subject": "Your weekly summary", "body": "..."}
try:
    gmail.run("send_email_message", args)
except noclick.ApprovalRequired as pending:
    if pending.operation.wait():
        gmail.run("send_email_message", args, approval_id=pending.approval_id)

lookupasync

connection.lookup(
    field, arguments=None, *, search=None, page_token=None
) -> LookupOptions

List the allowed values of an ID-like field (channels, repos, sheets) in this account.

Parameters
fieldstrrequired
argumentsdict[str, Any] | Nonedefault None
searchstr | Nonedefault None
page_tokenstr | Nonedefault None
Returns
LookupOptions

One page of options, each with a value to pass and a label to show; next_page_token is set when there are more.

Usage

Fill a picker from the user's own Slack:

slack = noclick.Connection.from_name("slack", user="user_42")
for option in slack.lookup("channel", search="gen"):
    print(option.value, option.label)

A lookup that depends on another argument (a sheet inside a spreadsheet) takes it in arguments; pass next_page_token back as page_token for more:

sheets = noclick.Connection.from_name("sheets", user="user_42")
spreadsheet = {"spreadsheet_id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"}
tabs = sheets.lookup("sheet_name", spreadsheet)
if tabs.next_page_token:
    more = sheets.lookup("sheet_name", spreadsheet, page_token=tabs.next_page_token)

verifyasync

connection.verify() -> Verification | None

Re-check the account against the provider.

Returns
Verification | None

The provider's verdict, also kept as verification: reachable is True when it accepted the account, False when it refused it, and None when NoClick can't tell; message says what was found.

Usage
gmail = noclick.Connection.from_name("gmail", user="user_42")
check = gmail.verify()
print(check.reachable, check.message)   # True  Signed in as ana@example.com.

require_approvalasync

connection.require_approval(*operations) -> Connection

From now on its owner approves each call of these operations, wherever it runs: your code, agents, and the app's workflows. "*" means every operation. It only ever adds to approval_operations; lifting one is done in the app. On one shared with your account, noclick.PermissionDenied.

Parameters
*operationsstr
Returns
Connection

The connection, with its approval_operations.

Usage
gmail = noclick.Connection.from_name("gmail")
gmail.require_approval("send_email_message", "delete_message")
print(gmail.approval_operations)   # ['delete_message', 'send_email_message']

mark_test_accountasync

connection.mark_test_account(test_account=True) -> Connection

Mark it as a test account (or, with False, not one). An app's preview only reads through a connection; through a test account it writes as well, as production does. Only the account that connected it marks it (noclick.PermissionDenied on one shared with yours), and it must hold an account (noclick.InvalidRequest while pending).

Parameters
test_accountbooldefault True
Returns
Connection

The connection, with test_account set.

Usage
slack = noclick.Connection.from_name("slack-sandbox")
slack.mark_test_account()
print(slack.test_account)   # True

tokenasync

connection.token() -> Token

Mint a fresh OAuth access token for calling the provider yourself. A model provider's connection never hands out its key or plan token (noclick.PermissionDenied).

Returns
Token

The access token, its token_type and when it expires_at; never a refresh token or an API key.

Usage
gmail = noclick.Connection.from_name("gmail", user="user_42")
token = gmail.token()
headers = {"Authorization": f"{token.token_type} {token.access_token}"}

deleteasync

connection.delete() -> None

Delete the connection, revoking its account at the provider where possible.

Usage
noclick.Connection.from_name("gmail", user="user_42").delete()

run

noclick.run(ref, arguments=None, *, approval_id=None, user=..., client=None) -> Any

Run one operation by its ref, "name.operation", as tools= names it. name is a connection in the scope the call acts for (the user's, else the account's), else an integration whose operation needs no account, which then runs on NoClick's key with no connection at all. A connection named like the integration (exa, holding your own key) wins, so the same ref then runs on your key. An account connected in the app is "credential:<id>.operation".

It raises like connection.run: noclick.NeedsConnection (or noclick.CredentialDisconnected) with the connect_url when the connection has no working account, noclick.InvalidRequest when the operation needs an account and no connection has that name, noclick.NotFound when name is neither a connection nor an integration, noclick.PolicyDenied or noclick.ApprovalRequired (retry with its approval_id= once .operation.wait() is True), and noclick.UpstreamError when the service refuses the call (its own words, its HTTP status in upstream_status).

Parameters
refstrrequired
argumentsdict[str, Any] | Nonedefault None
approval_idstr | 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
Any

The operation's data, as plain data (what the service answered).

Usage

Search the web with nothing to create or connect, on NoClick's key:

results = noclick.run("exa.search", {"query": "invoice automation for small businesses"})

A name that is a connection runs through it, here the user's own Gmail:

with noclick.as_user("user_42"):
    noclick.run("gmail.send_email_message", {
        "to": ["ana@example.com"],
        "subject": "Your weekly summary",
        "body": "Three invoices were paid this week.",
    })

Bring your own key: a connection named exa holding it takes over the same ref:

noclick.Connection.create("exa", credential_type="exa_api_key", data={"api_key": "..."})
results = noclick.run("exa.search", {"query": "invoice automation for small businesses"})

When your policy holds the call for a person's approval, wait for it and repeat the identical call with the approval's id:

args = {"to": ["ana@example.com"], "subject": "Your weekly summary", "body": "..."}
try:
    noclick.run("gmail.send_email_message", args, user="user_42")
except noclick.ApprovalRequired as pending:
    if pending.operation.wait():
        noclick.run("gmail.send_email_message", args, user="user_42", approval_id=pending.approval_id)

ConnectionOperations

class ConnectionOperations

Some of a connection's operations as tools (connection.only(...)), with the arguments bind(...) fixed.

Attributes
connectionConnection
operationslist[str]
bindingsdict[str, Any]

The arguments NoClick fills on every call, out of the model's reach.

bind

connection_operations.bind(**arguments) -> ConnectionOperations

Fix arguments of these operations: a bound parameter leaves the tool's schema, so the model never sees or sets it, and NoClick fills it on every call. A value may name the user the call acts for: {{user.id}}, {{user.email}}, {{user.name}} or {{user.metadata.<key>}}. A bound value always wins, over what a model passes through in an operation's free-form parameters too; a tool whose binding can't be filled (a user with no email, say) isn't offered that turn. Policy judges the call's final arguments. Every operation listed must take each bound parameter. Works wherever tools do: agents, toolkits and hosted MCP servers. Sends nothing.

Parameters
**argumentsAny
Returns
ConnectionOperations

A new ref with these arguments bound on top of any bound before.

Usage

An assistant that can email only its own user, whatever a message it reads says:

with noclick.as_user("user_42"):
    gmail = noclick.Connection.from_name("gmail", integration="gmail", create_if_missing=True)
    assistant = noclick.Agent.from_name(
        "assistant",
        create_if_missing=True,
        instructions="You summarize the user's inbox and email them the summary.",
        tools=[gmail.only("fetch_emails_from_inbox"), gmail.only("send_email_message").bind(to="{{user.email}}")],
    )

Pin a shared account connection to each user's own records:

hubspot = noclick.Connection.from_name("hubspot")
crm = hubspot.only("search_contacts").bind(owner_id="{{user.metadata.hubspot_owner}}")
toolkit = noclick.Toolkit([crm, "search"])

Verification

class Verification

Whether the provider accepted the credential (reachable None = cannot judge).

Attributes
reachablebool | None
sampleslist[Any]
messagestr | None
hintstr | None
rawdict[str, Any]

The response JSON exactly as the API sent it.

Token

class Token

A short-lived OAuth access token (never a refresh token or API key).

Attributes
access_tokenstr
token_typestr | None
expires_atAny
rawdict[str, Any]

The response JSON exactly as the API sent it.

LookupOption

class LookupOption
Attributes
valueAny
labelstr | None
rawdict[str, Any]

The response JSON exactly as the API sent it.

LookupOptions

class LookupOptions(list[LookupOption])

A page of options; pass next_page_token back as page_token.

Attributes
next_page_tokenstr | None

IntegrationInfo

class IntegrationInfo

A service NoClick can operate, such as gmail, or a model provider an agent thinks with, such as anthropic: one of Connection.integrations(). Its name is what integration= takes.

Attributes
namestr
labelstr | None
kindstr

"app", a service whose operations are tools, or "model", a model provider whose connection an agent names as its model_connection (never in tools=).

subscriptionbool

Whether its connect link also offers signing in with the provider's plan (a Claude or ChatGPT subscription) instead of pasting a key. Only a user's own connection takes a plan.

connect_withlist[str]

How a person connects an account: "oauth" (signing in), "key" (pasting a key or token), "qr" (scanning a code) or "purchase" (a phone number, bought rather than connected).

operation_countint | None
account_free_operation_countint

How many of its operations need no account (0 for most): noclick.run and tools= run them on NoClick's key by the integration's name ("exa.search"), with no connection.

credential_typeslist[str]

The exact credential types it takes, for credential_type= when you import a key you hold (connection.import_, Connection.create).

rawdict[str, Any]

The response JSON exactly as the API sent it.

IntegrationOperation

class IntegrationOperation

One operation of a connection's integration, with the JSON schema of the parameters connection.run takes for it.

Attributes
namestr
descriptionstr | None
parametersdict[str, Any]
read_onlybool | None

A hint from the operation's name (reads start with list_, get_ and the like), not a guarantee.

scopeslist[str]

The OAuth scopes it needs, where NoClick knows them.

needs_accountbool

False when it runs on NoClick's key with no account at all (web search, public data), so it runs with no connection ("exa.search") and on a pending one; True otherwise, also when the need depends on its arguments.

rawdict[str, Any]

The response JSON exactly as the API sent it.

IntegrationEvent

class IntegrationEvent

An app event a connection's integration can deliver to an agent's noclick.OnEvent trigger or a channel's subscription: its name (channel_message, contact_created), what it means, and filters, the keyword arguments that narrow it, each with what it matches (one marked Required. must be given).

Attributes
namestr
descriptionstr | None
filtersdict[str, str]
rawdict[str, Any]

The response JSON exactly as the API sent it.