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 ConnectionA named connection to an account on another service. Secrets never come
back; token() mints an OAuth access token (credentials:token scope).
status is pending until someone connects an account at connectUrl,
connected after, and disconnected once it's revoked (connectLink()
mints a link to reconnect it).
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 requireApproval 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.
- Like every live object, it also has hydrate(), refresh() and raw.
| id | string | |
| name | string | |
| integration | string | |
| credentialType | string | null | |
| userId | string | null | |
| status | ConnectionStatus | |
| origin | ConnectionOrigin |
|
| access | ConnectionAccess | Your account's access: |
| approvalOperations | string[] | Operations its owner approves call by call wherever it runs ( |
| account | string | null | The connected account as the provider named it, when proven. |
| testAccount | boolean | Whether it's a test account, which an app's preview writes through as well as reads. |
| usedBy | ConnectionHolder[] | What holds it: agents (in their tools, as their |
| connectedAt | Date | null | |
| createdAt | Date | null | |
| updatedAt | Date | null | |
| connected | boolean | |
| connectUrl | string | null | The open link where someone connects (or reconnects) the account, if one is open. |
| verification | Verification | null | |
| consoleUrl | string | null | This connection's kind in the developer console (null off the hosted API). |
fromNamestatic
static fromName(
name: string,
options?: ConnectionCreateOptions & { createIfMissing?: boolean },
): ConnectionA lazy reference to the connection called name in the current (or
given) user. With createIfMissing it's created on first use, for
integration: holding data when given (a key you hold), pending with a
connect link otherwise. An existing connection is used as it is. Given integration,
operations(), operation() and events() read the catalog without
loading or creating the connection.
Its integration: a name ("gmail") or an IntegrationInfo.
A credential you hold: the connection holds it at once (validated and probed first).
Also deliver the link: "user", "owner" (your account's owner), an email, or an object.
undefined = the ambient user (asUser), null = the account itself.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
A connection that loads, or is created, the first time it's used. Nothing is sent yet.
fromIdstatic
static fromId(connectionId: string, options?: CallOptions): ConnectionA lazy reference by NoClick id; credential:<id> for an account connected in the app.
A connection that loads the first time you read a field. Nothing is sent yet.
createstatic
static create(
name: string,
options?: ConnectionCreateOptions,
): Promise<Connection>Create a connection called name (unique per user; a duplicate throws
Conflict with existingId). With data it holds that credential at
once; without, it's pending and connectUrl is where someone connects it.
Its integration: a name ("gmail") or an IntegrationInfo.
A credential you hold: the connection holds it at once (validated and probed first).
Also deliver the link: "user", "owner" (your account's owner), an email, or an object.
undefined = the ambient user (asUser), null = the account itself.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
The new connection: pending with its connect_url, or connected
(with its verification) when you passed data.
getstatic
static get(ref: string, options?: CallOptions): Promise<Connection>The connection with this id or name.
The connection, loaded.
liststatic
static list(
options?: ReadOptions & ConnectionFilters & { limit?: number },
): Promise<Connection[]>The current user's connections (the account's own outside asUser), or those userFilter 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.
const fromTheApp = await Connection.list({ origin: "app" });Return at most this many.
Which users the read covers (user_id query), instead of acting as one (user).
undefined = the ambient user (asUser), null = the account itself.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
Every matching connection (or the first limit), fetched page by page.
listPagestatic
static listPage(
options?: ReadOptions & PageOptions & ConnectionFilters,
): Promise<Page<Connection>>One page of connections; pass nextCursor back as cursor.
The next_cursor of the page before; omit it for the first page.
Return at most this many.
Which users the read covers (user_id query), instead of acting as one (user).
undefined = the ambient user (asUser), null = the account itself.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
A page whose data holds the connections and whose next_cursor is None on the last page.
integrationsstatic
static integrations(
query?: string,
options?: { kind?: IntegrationKind; client?: NoClick },
): Promise<IntegrationInfo[]>The services a connection can be made to and the model providers agents think with: every
integration, or those whose name or label contains query (case-insensitive), each with how a
person connects it (connectWith) and how many of its operations run with no account, by its
name (accountFreeOperationCount). kind: "model" keeps the model providers an agent's
modelConnection names; kind: "app" the services whose operations are tools.
const [crm] = await Connection.integrations("hubspot");
const hubspot = Connection.fromName("crm", { integration: crm, createIfMissing: true });
const providers = await Connection.integrations(undefined, { kind: "model" });The client to send with; defaults to the module client built from NOCLICK_API_KEY.
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).
only
only(...operations: string[]): ConnectionOperationsA tool ref to some of its operations, for tools (gmail.only("send_email_message"));
a lazy createIfMissing connection is still created first.
A ref to just those operations of this connection, to list in tools=; bind(...) on it fixes
arguments the model mustn't choose.
checkout
checkout(repository: string, options?: CheckoutOptions): RepoCheckoutOne of its repositories (owner/name on its git host; a github connection) checked out in an
agent's workspace, for Agent repos: cloned into /workspace/ (the repository's name
when omitted) at a conversation's first turn. The agent's git reaches that repository only,
through NoClick, with a key made for its sandbox; 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 it the checkout is read-only. gh only reads it; give the agent the connection's
operations for pull requests and issues. Sends nothing.
const github = Connection.fromName("github", { integration: "github", createIfMissing: true });
const repos = [github.checkout("acme/docs", { push: "noclick/*" })];The branch it checks out (the repository's default when omitted).
The folder under /workspace it's cloned into (the repository's name when omitted).
Branches it may push to, exact or a prefix ending in /* ("noclick/*"); without it the checkout is read-only.
A RepoCheckout to list in an agent's repos.
operations
operations(options?: { search?: string }): Promise<IntegrationOperation[]>What its integration can do: every operation, or those whose name or description contains
search (case-insensitive), with parameter schemas and whether each needsAccount.
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.
operation
operation(name: string): Promise<IntegrationOperation>One of its integration's operations; throws NotFound for a name it doesn't have.
The operation, with the JSON schema of the parameters that run takes for it.
events
events(): Promise<IntegrationEvent[]>The app events its integration can wake an agent with, for OnEvent (what it pushes as it happens, never a poll).
Every event, with its description and the filters it takes
(channel for a Slack message, repository for a GitHub issue).
connectLink
connectLink(options?: LinkOptions): Promise<ConnectLink>A new link where someone connects (or reconnects) this connection's
account; await link.wait() resolves to the connected Connection. The
connection's own connectUrl is now this link's.
Also deliver the link: "user", "owner" (your account's owner), an email, or an object.
The link: its url, when it expires_at, and where it went when
you set send_to. It's also the operation that finishes when
someone connects: link.wait() returns the connected Connection.
The connection's own connect_url is now this link's.
request
request(options?: LinkOptions): Promise<ConnectLink>Ask the user (or sendTo) to connect it; await link.wait() resolves to the Connection. A
connection of your account's own has no user: send it to "owner" (your account's owner) or an email.
Also deliver the link: "user", "owner" (your account's owner), an email, or an object.
"user")The link, already sent: delivered_via says where it went, and
.wait() on it returns the connected Connection.
wait
wait(options?: { timeoutS?: number }): Promise<this>Resolve once an account is connected (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
(connectLink() or request(), from anywhere) hands the wait to the fresh
one. Throws OperationExpired when the open link lapses with no fresh one,
or OperationTimeout after timeoutS seconds.
This connection, connected. After a wait it's reloaded, so account
names the account when the provider says who it is.
import
import(data: JsonObject, options?: { credentialType?: string }): Promise<this>Replace the connection's credential with one you hold (a rotated key), validated first.
This connection, holding the new key, with the probe's verification.
The key it replaced is deleted.
run
run(
operation: string,
args?: JsonObject,
options?: { approvalId?: string },
): Promise<R>Run one of the integration's operations in this account. Throws
NeedsConnection while pending (an operation whose needsAccount is
false runs anyway, on NoClick's key, or on your own key once connected, and needs no
connection at all through run) and CredentialDisconnected once
revoked (both carry connectUrl); a policy may throw PolicyDenied or ApprovalRequired.
Resolves to the operation's data (what the service answered); a service that refuses the call
throws UpstreamError in its own words, its HTTP status in details.upstream_status.
The operation's data, as plain data (what the service answered).
lookup
lookup(
field: string,
options?: { arguments?: JsonObject; search?: string; pageToken?: string },
): Promise<LookupOptions>The allowed values of an ID-like field (channels, repos, sheets) in this account.
One page of options, each with a value to pass and a label to show;
next_page_token is set when there are more.
verify
verify(): Promise<Verification | null>Re-check the account against the provider.
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.
requireApproval
requireApproval(...operations: string[]): Promise<this>From now on its owner approves each call of these operations wherever it runs (your code, agents, the
app's workflows); "*" means every operation. It only adds to approvalOperations; lifting one is done
in the app. On one shared with your account it throws PermissionDenied.
await Connection.fromName("gmail").requireApproval("send_email_message", "delete_message");The connection, with its approval_operations.
markTestAccount
markTestAccount(testAccount?): Promise<this>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 (PermissionDenied on one shared with yours), and it must hold an account (InvalidRequest while
pending).
await Connection.fromName("slack-sandbox").markTestAccount();trueThe connection, with test_account set.
token
token(): Promise<Token>A fresh OAuth access token for calling the provider yourself (credentials:token scope). A model
provider's connection never hands out its key or plan token (PermissionDenied).
The access token, its token_type and when it expires_at; never a
refresh token or an API key.
delete
delete(): Promise<void>Delete the connection, revoking its account at the provider where possible.
run
function run(
ref: string,
args?: JsonObject,
options?: CallOptions & { approvalId?: string },
): Promise<R>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".
Throws like connection.run: NeedsConnection (or CredentialDisconnected) with connectUrl
when the connection has no working account, InvalidRequest when the operation needs an account
and no connection has that name, NotFound when name is neither a connection nor an
integration, PolicyDenied or ApprovalRequired (retry with its approvalId once
err.operation.wait() resolves to true), and UpstreamError when the service refuses the call
(its own words, its HTTP status in details.upstream_status). Resolves to the operation's data.
const results = await run("exa.search", { query: "invoice automation for small businesses" });
await run("gmail.send_email_message", { to: ["ana@example.com"], subject: "Hi", body: "..." }, { user: "user_42" });The operation's data, as plain data (what the service answered).
ConnectionOperations
class ConnectionOperationsSome of a connection's operations as tools (connection.only(...)), with the arguments bind(...) fixed.
| connection | Connection | |
| operations | string[] | |
| bindings | JsonObject | The arguments NoClick fills on every call, out of the model's reach. |
bind
bind(args: JsonObject): ConnectionOperationsFix 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 isn't offered that
turn. Policy judges the final arguments. Works in agents, toolkits and hosted MCP servers.
gmail.only("send_email_message").bind({ to: "{{user.email}}" })A new ref with these arguments bound on top of any bound before.
ConnectLink
class ConnectLink extends OperationA connect page URL that is also the operation tracking it.
- Inherits from Operation.
| url | string | Where the person connects. |
| expiresAt | Date | null | |
| connection | string | null | The connection the link fills. |
| deliveredVia | string[] | Channels the link went out on when |
| undelivered | JsonObject[] | Channels that failed, each with a |
liststatic
static list(
options?: KindScopedFilters & { limit?: number },
): Promise<Operation<Connection>[]>Connect-link operations (kind credential.connect), newest first. Rows are
operations: the link's URL is only in the response that created it (and on its pending connection).
Return at most this many.
undefined = the ambient user (asUser), null = the account itself.
Statuses, or "live" for every non-terminal one.
An Agent or agent id.
The requesting key: its id or its prefix (nk_live_ab12).
Rows changed since (for polling).
Which users the read covers (user_id query), instead of acting as one (user).
Every matching operation (or the first limit), fetched page by page;
wait() on one returns the connected Connection.
listPagestatic
static listPage(
options?: KindScopedFilters & PageOptions,
): Promise<Page<Operation<Connection>>>One page of connect-link operations; pass nextCursor back as cursor.
The next_cursor of the page before; omit it for the first page.
Return at most this many.
undefined = the ambient user (asUser), null = the account itself.
Statuses, or "live" for every non-terminal one.
An Agent or agent id.
The requesting key: its id or its prefix (nk_live_ab12).
Rows changed since (for polling).
Which users the read covers (user_id query), instead of acting as one (user).
A page whose data holds the operations and whose next_cursor is None on the last page.
summarystatic
static summary(options?: KindScopedFilters): Promise<OperationSummary>How many connect-link operations match, by status.
undefined = the ambient user (asUser), null = the account itself.
Statuses, or "live" for every non-terminal one.
An Agent or agent id.
The requesting key: its id or its prefix (nk_live_ab12).
Rows changed since (for polling).
Which users the read covers (user_id query), instead of acting as one (user).
The total that match and a count for every status in by_status.
Verification
interface VerificationWhether the provider accepted the credential (reachable null = cannot judge).
| reachable | boolean | null | |
| samples | unknown[] | |
| message | string | null | |
| hint | string | null | |
| raw | JsonObject | The response JSON exactly as the API sent it. |
Token
interface TokenA short-lived OAuth access token (never a refresh token or API key).
| accessToken | string | |
| tokenType | string | null | |
| expiresAt | Date | null | |
| raw | JsonObject | The response JSON exactly as the API sent it. |
LookupOption
interface LookupOption| value | unknown | |
| label | string | null | |
| raw | JsonObject | The response JSON exactly as the API sent it. |
LookupOptions
interface LookupOptionsA page of options; pass nextPageToken back as pageToken.
| nextPageToken | string | null |
optionsTypeScript only
options: LookupOption[]IntegrationInfo
interface IntegrationInfoA service in the catalog (Connection.integrations()), such as gmail, or a model provider such as anthropic.
| name | string | |
| label | string | null | |
| kind | IntegrationKind |
|
| subscription | boolean | 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. |
| connectWith | string[] | How a person connects an account: |
| operationCount | number | null | |
| accountFreeOperationCount | number | How many of its operations need no account (0 for most): |
| credentialTypes | string[] | The exact credential types: the |
| raw | JsonObject | The response JSON exactly as the API sent it. |
IntegrationOperation
interface IntegrationOperationOne operation of an integration, with its JSON-schema parameters.
| name | string | |
| description | string | null | |
| parameters | JsonObject | |
| readOnly | boolean | null | A hint from the operation's name (reads start with |
| scopes | string[] | The OAuth scopes it needs, where NoClick knows them. |
| needsAccount | boolean | False when it runs on NoClick's key with no account at all (web search, public data), so it runs
with no connection ( |
| raw | JsonObject | The response JSON exactly as the API sent it. |
IntegrationEvent
interface IntegrationEventAn app event an integration can deliver to an agent's OnEvent trigger or a channel's subscription:
its name (channel_message, contact_created), what it means, and the filters that narrow it, each
with what it matches (one marked Required. must be given).
| name | string | |
| description | string | null | |
| filters | Record<string, string> | |
| raw | JsonObject | The response JSON exactly as the API sent it. |