AI

AI: models paid from your NoClick balance, for code that isn't an agent, and your agents as models.

reply = noclick.AI.chat("Summarize this in one line: ...")
vectors = noclick.AI.embed(["first", "second"]).vectors
image = noclick.AI.image("A lighthouse at dusk, watercolor")
clip = noclick.AI.video("Waves rolling under a lighthouse at dusk").wait()
words = noclick.AI.transcribe("https://example.com/call.mp3")
answer = noclick.AI.chat("Where's my refund?", model="agent:support")

Every call is charged in US dollars to your account; inside as_user it is attributed to that user and counts against its spend cap. AI.base_url serves chat completions, responses, embeddings and the model list to any OpenAI client.

class AI

Models paid from your NoClick balance, for code that isn't an agent: chat, embeddings, images, videos and transcription; and your agents as agent:<name> models. Model ids are OpenRouter ids with the openrouter/ prefix; leave model out for NoClick's default of each kind.

Attributes
base_urlstr

The OpenAI-compatible endpoint (https://api.noclick.com/v1/ai on the hosted API): point any OpenAI client at it with your NoClick key for chat completions, responses, embeddings and the model list.

chatclassmethodasync

noclick.AI.chat(
    messages, *, model=None, max_tokens=None, temperature=None, response_format=None,
    tools=None, conversation=None, user=..., client=None
) -> ChatReply

Send a conversation to a chat model: a list of {"role", "content"} messages, or a string as one user message. response_format asks for JSON ({"type": "json_schema", "json_schema": ...}); tools are OpenAI-format tool definitions, such as toolkit.definitions("openai").

model="agent:<name>" talks to one of your agents instead, which brings its own tools (so tools is refused) and keeps the conversation: pass conversation=reply.conversation to continue it, and only the messages after the last assistant message are sent. The answer streams, so a long turn never times out. A request with no conversation whose messages end in an assistant reply the agent gave recently continues that reply's conversation (in the same user), so a chat UI that resends the whole history keeps one thread (invite-only beta).

Parameters
messagesstr | Sequence[Any]required
modelstr | Nonedefault None
max_tokensint | Nonedefault None
temperaturefloat | Nonedefault None
response_formatdict[str, Any] | Nonedefault None
toolsSequence[dict[str, Any]] | Nonedefault None
conversationstr | 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
ChatReply

The answer: its text, the message to append to the conversation, and the tokens it used and what it cost.

Usage

Ask for one answer:

reply = noclick.AI.chat(
    [
        {"role": "system", "content": "You write one-line summaries."},
        {"role": "user", "content": "Summarize: the meeting moved to Friday at 3pm."},
    ],
    model="openrouter/anthropic/claude-sonnet-4.6",
    max_tokens=200,
)
print(reply.text, reply.cost_usd)

Let the model use your users' connections, with a Toolkit:

toolkit = noclick.Toolkit(["gmail", "search"])
messages = [{"role": "user", "content": "Summarize the emails Ana sent me this week"}]
with noclick.as_user("user_42"):
    while True:
        reply = noclick.AI.chat(messages, tools=toolkit.definitions("openai"))
        messages.append(reply.message)
        if not reply.tool_calls:
            break
        messages += toolkit.handle_openai(reply.message)
print(reply.text)

Talk to one of your agents, then keep going in its conversation:

reply = noclick.AI.chat("Where's my refund?", model="agent:support")
follow_up = noclick.AI.chat("And how long will it take?", model="agent:support",
                            conversation=reply.conversation)
print(follow_up.text)

embedclassmethodasync

noclick.AI.embed(
    texts, *, model=None, dimensions=None, user=..., client=None
) -> Embeddings

Turn text into vectors for search and clustering: one string, or a list of up to 2,048. dimensions shortens the vectors, for models that allow it.

Parameters
textsstr | Sequence[str]required
modelstr | Nonedefault None
dimensionsint | 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
Embeddings

One vector per text, in order, with their length and what the call cost.

Usage
docs = ["Refunds take 5 days.", "We ship worldwide.", "Support is open 9 to 5."]
result = noclick.AI.embed(docs, model="openrouter/openai/text-embedding-3-small")
print(result.dimensions, len(result.vectors))   # 1536 3

[query] = noclick.AI.embed("How long do refunds take?", model="openrouter/openai/text-embedding-3-small").vectors

imageclassmethodasync

noclick.AI.image(
    prompt, *, model=None, size=None, user=..., client=None
) -> GeneratedImage

Make one image from prompt; size is "WIDTHxHEIGHT" in an aspect ratio the model makes (the model's own shape unless you say).

Parameters
promptstrrequired
modelstr | Nonedefault None
sizestr | 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
GeneratedImage

The image, kept as one of your account's files: a url that keeps working, its file_id, its size and what it cost.

Usage
image = noclick.AI.image("A lighthouse at dusk, watercolor", size="1024x1536")
print(image.url, image.width, image.height, image.cost_usd)

videoclassmethodasync

noclick.AI.video(
    prompt, *, model=None, seconds=None, resolution=None, aspect_ratio=None,
    audio=True, user=..., client=None
) -> Operation

Start one video from prompt. It takes minutes, so it runs as an operation. seconds, resolution (720p, 1080p) and aspect_ratio (16:9, 9:16) are among the ones the model makes; left out, about 8 seconds at 720p. audio adds sound where the model makes it. Its price is quoted before it starts (usd_per_second in AI.models(kind="video")) and charged when it's done.

Parameters
promptstrrequired
modelstr | Nonedefault None
secondsint | Nonedefault None
resolutionstr | Nonedefault None
aspect_ratiostr | Nonedefault None
audiobooldefault True
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
Operation

An Operation at once, whose wait() gives the GeneratedVideo, kept as one of your account's files.

Usage
job = noclick.AI.video("Waves rolling under a lighthouse at dusk", seconds=8, aspect_ratio="9:16")
clip = job.wait(timeout=600)
print(clip.url, clip.seconds, clip.cost_usd)

transcribeclassmethodasync

noclick.AI.transcribe(
    audio, *, model=None, language=None, speakers=False, background=False, user=...,
    client=None
) -> Transcript | Operation

Write down what is said in a recording: an https URL to it, or its bytes, up to 2 hours (25 MB as bytes, 200 MB behind a URL), in MP3, WAV, M4A, MP4, OGG, WebM, AAC, FLAC or AIFF. language (an ISO 639-1 code) skips detection. The default model transcribes a recording over 10 minutes in pieces cut at pauses, each charged, and joins them into one transcript; a failed piece fails the call and says which.

speakers (invite-only beta) tells the voices apart. It runs on noclick/transcribe-1, which takes the whole recording in one job, so each voice keeps its label from start to end (a model that cuts recordings into pieces refuses it). True labels each segment's speaker speaker_1, speaker_2 and on, in the order they first speak. A list of names (["rep", "customer"]) says how many voices there are, and NoClick works out which is which from what they say (a rep introduces the company, say); a voice it can't tell takes the next unused name in speaking order. It's priced per minute as usd_per_minute_with_speakers in AI.models.

background=True (invite-only beta) returns an Operation at once: for a recording that takes longer than one request waits (90 seconds), as a recording with speakers over about an hour can. It runs on noclick/transcribe-1 too. A call that doesn't run in the background and passes 90 seconds raises RequestTimeout naming its operation, which still finishes, is charged, and holds the transcript.

Parameters
audiostr | bytesrequired
modelstr | Nonedefault None
languagestr | Nonedefault None
speakersbool | Sequence[str]invite-only betadefault False
backgroundboolinvite-only betadefault False
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
Transcript | Operation

The transcript: its text, timed segments, the language and how long the audio was. With background=True, an Operation at once, whose wait() gives it.

Usage

From a URL:

words = noclick.AI.transcribe("https://example.com/calls/2026-09-26.mp3")
print(words.language, words.duration_s)
print(words.text)

From a file you have:

with open("voicemail.m4a", "rb") as f:
    words = noclick.AI.transcribe(f.read(), language="en")
for segment in words.segments:
    print(f"{segment.start_s:6.1f}s  {segment.text}")

Who said what on a sales call (invite-only beta):

call = noclick.AI.transcribe("https://example.com/calls/demo.mp3", speakers=["rep", "customer"])
for segment in call.segments:
    print(segment.speaker, segment.text)

A two-hour meeting, in the background (invite-only beta):

job = noclick.AI.transcribe("https://example.com/meetings/all-hands.mp3", speakers=True,
                            background=True)
meeting = job.wait()
print(len(meeting.segments), meeting.cost_usd)

headersclassmethodasync

noclick.AI.headers(*, user=..., client=None) -> dict[str, str]

The headers a call to AI.base_url needs: your key as the bearer and, inside as_user (or with user=), the NoClick-User that attributes the call to that user and holds it to the user's spend cap. Give them to an OpenAI client as default_headers; without the user header the account pays and no user's cap applies. A client from a user session gets the session's token.

Parameters
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[str, str]

Authorization and, when a user is in scope, NoClick-User.

Usage

One OpenAI client per user, talking to that user's agent:

from openai import OpenAI

with noclick.as_user("user_42"):
    headers = noclick.AI.headers()   # the key rides in these, so api_key is a placeholder
gpt = OpenAI(base_url=noclick.AI.base_url, api_key="-", default_headers=headers)
reply = gpt.chat.completions.create(model="agent:assistant", messages=[{"role": "user", "content": "Hi"}])

modelsclassmethodasync

noclick.AI.models(*, kind=None, user=..., client=None) -> list[AIModel]

The models you can call (kind: chat, embedding, image, transcription, video or agent); agent lists the agents you (or the user you act for) can talk to as agent:<name>.

Parameters
kindstr | 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
list[AIModel]

Every matching model with its prices in US dollars, the default of each kind first.

Usage
for m in noclick.AI.models(kind="chat"):
    print(m.id, m.context_length, m.input_usd_per_token, m.output_usd_per_token)

ChatReply

class ChatReply

A model's (or an agent's) answer: text, and the whole message to append to the conversation.

Attributes
textstr

The answer as text (empty when the model only called tools).

messagedict[str, Any]

The assistant message as the API sent it (role, content, tool_calls).

modelstr | None
finish_reasonstr | None

Why the model stopped: stop, length (max_tokens reached) or tool_calls.

usagedict[str, int]

Tokens used: prompt_tokens, completion_tokens, total_tokens (none for an agent).

cost_usdfloat | None

What the call cost, in US dollars.

rawdict[str, Any]

The response JSON exactly as the API sent it.

tool_callslist[dict[str, Any]]

The tools the model asked to call (empty if none), in the OpenAI shape toolkit.handle_openai runs. An agent runs its own tools, so its reply never has any.

conversationstr | None

An agent's conversation this reply is part of: pass it as conversation= to continue it.

Embeddings

class Embeddings

One vector per input, in the order you passed them.

Attributes
vectorslist[list[float]]
dimensionsint

The length of each vector.

modelstr | None
usagedict[str, int]

Tokens used: prompt_tokens, total_tokens.

cost_usdfloat | None

What the call cost, in US dollars.

rawdict[str, Any]

The response JSON exactly as the API sent it.

GeneratedImage

class GeneratedImage

An image a model made, kept as one of your account's files.

Attributes
urlstr

A link to the image that keeps working.

widthint | None
heightint | None
modelstr | None
cost_usdfloat | None

What the image cost, in US dollars.

expires_atAny

When url stops working: None, since the file is kept.

file_idstr | None

The file in your account's files.

rawdict[str, Any]

The response JSON exactly as the API sent it.

GeneratedVideo

class GeneratedVideo

A video a model made, kept as one of your account's files.

Attributes
urlstr

A link to the video that keeps working.

file_idstr | None

The file in your account's files.

mime_typestr | None
secondsint | None
resolutionstr | None
modelstr | None
cost_usdfloat | None

What the video cost, in US dollars.

rawdict[str, Any]

The response JSON exactly as the API sent it.

Transcript

class Transcript

What was said in a recording, as one text and as timed segments.

Attributes
textstr
languagestr | None

The spoken language as an ISO 639-1 code (en), detected unless you passed one.

duration_sfloat | None
segmentslist[TranscriptSegment]
modelstr | None
cost_usdfloat | None

What the transcription cost, in US dollars.

rawdict[str, Any]

The response JSON exactly as the API sent it.

TranscriptSegment

class TranscriptSegment

A stretch of speech: what was said between start_s and end_s, and by whom when you asked for speakers.

Attributes
start_sfloat
end_sfloat
textstr
speakerstr | None

Who said it: one of the names you gave speakers, or speaker_1, speaker_2, ... in the order the voices first speak (None unless you asked; invite-only beta).

AIModel

class AIModel

A model you can call, and what it costs in US dollars (its provider's own price). Only the prices for its kind are set: per token for chat and embedding models, per token of the image an image model makes, per minute of audio (and per token of the transcript) for transcription ones, per second for video ones, and none for an agent, whose turns cost what its model and tools do.

Attributes
idstr

The id to pass as model=, like openrouter/anthropic/claude-sonnet-4.6 or agent:support.

namestr | None
kindstr

chat, embedding, image, transcription or agent.

context_lengthint | None

The most tokens a request can hold (chat and embedding models).

input_usd_per_tokenfloat | None
output_usd_per_tokenfloat | None
usd_per_minutefloat | None
usd_per_minute_with_speakersfloat | None

What a minute of audio costs with speakers; None for a model that can't tell voices apart (invite-only beta).

usd_per_secondfloat | None

What a second of video costs, for the clip a call naming no length or resolution makes.

rawdict[str, Any]

The response JSON exactly as the API sent it.