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 AIModels 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.
| base_url | str | The OpenAI-compatible endpoint ( |
chatclassmethodasync
noclick.AI.chat(
messages, *, model=None, max_tokens=None, temperature=None, response_format=None,
tools=None, conversation=None, user=..., client=None
) -> ChatReplySend 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).
NoneNoneNoneNoneNoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The answer: its text, the message to append to the conversation, and
the tokens it used and what it cost.
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
) -> EmbeddingsTurn 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.
NoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)One vector per text, in order, with their length and what the call cost.
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").vectorsimageclassmethodasync
noclick.AI.image(
prompt, *, model=None, size=None, user=..., client=None
) -> GeneratedImageMake one image from prompt; size is "WIDTHxHEIGHT" in an aspect
ratio the model makes (the model's own shape unless you say).
NoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The image, kept as one of your account's files: a url that keeps working, its
file_id, its size and what it cost.
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
) -> OperationStart 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.
NoneNoneNoneNoneTrueWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)An Operation at once, whose wait() gives the GeneratedVideo, kept as one
of your account's files.
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 | OperationWrite 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.
NoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)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.
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.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Authorization and, when a user is in scope, NoClick-User.
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>.
NoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching model with its prices in US dollars, the default of each kind first.
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 ChatReplyA model's (or an agent's) answer: text, and the whole message to
append to the conversation.
| text | str | The answer as text (empty when the model only called tools). |
| message | dict[str, Any] | The assistant message as the API sent it ( |
| model | str | None | |
| finish_reason | str | None | Why the model stopped: |
| usage | dict[str, int] | Tokens used: |
| cost_usd | float | None | What the call cost, in US dollars. |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
| tool_calls | list[dict[str, Any]] | The tools the model asked to call (empty if none), in the OpenAI shape |
| conversation | str | None | An agent's conversation this reply is part of: pass it as |
Embeddings
class EmbeddingsOne vector per input, in the order you passed them.
| vectors | list[list[float]] | |
| dimensions | int | The length of each vector. |
| model | str | None | |
| usage | dict[str, int] | Tokens used: |
| cost_usd | float | None | What the call cost, in US dollars. |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
GeneratedImage
class GeneratedImageAn image a model made, kept as one of your account's files.
| url | str | A link to the image that keeps working. |
| width | int | None | |
| height | int | None | |
| model | str | None | |
| cost_usd | float | None | What the image cost, in US dollars. |
| expires_at | Any | When |
| file_id | str | None | The file in your account's files. |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
GeneratedVideo
class GeneratedVideoA video a model made, kept as one of your account's files.
| url | str | A link to the video that keeps working. |
| file_id | str | None | The file in your account's files. |
| mime_type | str | None | |
| seconds | int | None | |
| resolution | str | None | |
| model | str | None | |
| cost_usd | float | None | What the video cost, in US dollars. |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
Transcript
class TranscriptWhat was said in a recording, as one text and as timed segments.
| text | str | |
| language | str | None | The spoken language as an ISO 639-1 code ( |
| duration_s | float | None | |
| segments | list[TranscriptSegment] | |
| model | str | None | |
| cost_usd | float | None | What the transcription cost, in US dollars. |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
TranscriptSegment
class TranscriptSegmentA stretch of speech: what was said between start_s and end_s, and by
whom when you asked for speakers.
| start_s | float | |
| end_s | float | |
| text | str | |
| speaker | str | None | Who said it: one of the names you gave |
AIModel
class AIModelA 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.
| id | str | The id to pass as |
| name | str | None | |
| kind | str |
|
| context_length | int | None | The most tokens a request can hold (chat and embedding models). |
| input_usd_per_token | float | None | |
| output_usd_per_token | float | None | |
| usd_per_minute | float | None | |
| usd_per_minute_with_speakers | float | None | What a minute of audio costs with |
| usd_per_second | float | None | What a second of video costs, for the clip a call naming no length or resolution makes. |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |