talqing is the Python client for the /v1 API, generated from the same OpenAPI document as the TypeScript SDK. It carries every operation under the same names, sync and async.
Python 3.10 or newer. httpx is the only runtime dependency you will notice. The package ships a py.typed marker, so a type checker in your project sees every TypedDict it declares — no stub package and no # type: ignore.

Creating a client

A personal access token has no prefix. It is an opaque signed string: do not parse it and do not assume a shape. Talqing.from_env(**overrides) reads TALQING_API_KEY and TALQING_BASE_URL, and anything you pass wins over the environment:
AsyncTalqing has exactly the same shape with every operation a coroutine, and takes an httpx.AsyncBaseTransport:
Both are context managers, which is the shortest way to close the connection pool. Outside a with, call talqing.close()await talqing.close() on the async client. Two properties come with the client: talqing.base_url, and talqing.http, the underlying httpx.Client already carrying the base URL and the token.
The default timeout=30.0 is shorter than an agent task’s own budget. POST /v1/tasks/{task_id}/runs runs the task and waits for the result, and a task’s timeout_seconds defaults to 120 and may be set as high as 600. With the default client, a task that takes longer than thirty seconds raises httpx.ReadTimeout on your side while the run continues on ours — you lose the result, not the spend. Raise the timeout past the task’s own budget before you run one.

The call shape

Operations are reached by resource, exactly as the API names them. Path parameters are positional. Everything else — body fields and query parameters alike — is a keyword argument.

An argument you do not pass is not sent

Every optional argument defaults to a sentinel, OMIT, and an argument still at OMIT is left out of the request entirely. That is what makes an explicit null sayable — the distinction a PATCH needs and a plain None default would destroy:
OMIT is exported from talqing if you ever need to pass it deliberately, for instance when building arguments in a dict.

The one renamed method

telephony.phone_numbers.import_()import is a Python keyword. Everything else carries the API’s own name.

Errors

Every non-2xx raises TalqingAPIError. There is one error envelope, so there is nothing to branch on.
A failure that never reached the API — a proxy’s HTML 502, a gateway timeout — has no envelope to parse. It still raises TalqingAPIError, with the raw body text as the message and an empty errors. A connection error or a timeout raises httpx’s own exception, unchanged.

Pagination

paginate(page, *, limit=200, **filters) takes the bound method itself, not a call to it, and passes anything else straight through as a filter. It stops when has_more is false. paginate_async is the awaited form, driven with async for. See pagination.

Streams

The six event-stream endpoints return a Stream — an iterator of decoded frames, and a context manager. Opening is not a coroutine on either client, so async for reads exactly the way for does.
Use the context manager whenever the loop may exit early — leaving it closes the connection. Iterating without one also closes at the end of the loop; the difference matters on a break or an exception.
If the stream fails to open — 404, 401, 403 — the client reads the error body before anything else and raises TalqingAPIError immediately. A finished stream cannot be reopened: it raises RuntimeError: this stream is finished; open a new one to watch again. See streaming for every frame each one emits.

Typing

Responses come back as decoded JSON, described by TypedDicts. Nothing is validated, coerced or renamed on arrival. A response is a plain dict at runtime; the TypedDicts exist for your type checker and your editor and cost nothing when you run. Two consequences worth knowing:
  • Read fields with response["field"], not response.field.
  • The SDK can never reject a payload the API considers valid. A field added to a response after your version was generated arrives intact — your type checker will not know about it, and your code still gets it.
Every request and response shape is exported under the API’s own name:

Escape hatches

request(method, path, *, query=None, body=None) applies the same OMIT filtering and the same error handling as a generated method, and returns the decoded body.

The namespaces

Every method under them is in the Endpoints section of the API reference. The path here is the operation’s id there: telephony.phone_numbers.assign is POST /v1/telephony/phone-numbers/{number_id}/assign.

Worked examples

Create and publish an agent

agents.update takes the complete config, not a patch — read the agent, change what you need, send the whole object back:

Place an outbound call

This places a live, billable call to a real person. It returns when the call is placed, not when it is answered, and there is no idempotency key — a retried request dials again. See outbound calls.

Page through calls

Read the credit balance

The balance belongs to whichever region this client’s base_url points at. Credits do not move between regions, so label it with the region before you show it to anyone. See pricing and credits.

Talk to a text agent and watch the reply

client_message_id is your own UUID and is the one deduplication key in this API: re-sending with the same one is ignored rather than delivered twice, so a retry is safe.

Streaming

Every frame the six event streams emit.

Errors

Every status code and what to retry.