Skip to main content

nopaque - Python SDK for Nopaque

Official Python client for the Nopaque API.

Install

pip install nopaque

Quick start

from nopaque import Nopaque
from nopaque.models.mapping import MappingJobConfig

client = Nopaque(api_key="nop_live_...")   # or set NOPAQUE_API_KEY

# Create a mapping job and wait for it to complete
job = client.mapping.create(
    name="Main IVR",
    phone_number="+441234567890",
    config=MappingJobConfig(mapping_mode="dtmf"),
)
client.mapping.start(job.id)
final = client.mapping.wait_for_complete(job.id)
print(final.status)

# List audio files with automatic pagination
for audio in client.audio.list():
    print(audio.id, audio.file_name)

client.close()

Async

import asyncio
from nopaque import AsyncNopaque

async def main():
    async with AsyncNopaque() as client:
        job = await client.mapping.get("map_abc123")
        print(job.status)

asyncio.run(main())

Digital testing (beta)

Run automated chat-channel conversations against a bot and get a pass/fail verdict. Access is limited to beta workspaces during the beta period.

sector and mission are required — the API returns a 400 without them.

run = client.digital_testing.create(
    target_ref="acme/billing-bot",
    target={"transport": "web-widget", "url": "https://example.com/support"},
    sector="utilities",
    mission="check the outstanding balance",
    kind="freeform",
    acceptance="The bot states the outstanding balance.",
)

finished = client.digital_testing.wait_for_run(run.id)
# `completed` with outcome "fail" is a RESULT, not an error.
print(finished.status, finished.outcome, finished.pass_rate)

Reusable configs live under client.digital_test_configs (create, list, get, update, delete, launch), and client.digital_compliance lists compliance audits and fetches a per-target report.

Survey tests

A survey test runs the other way round from a call test: your survey platform starts the conversation, and a nopaque respondent answers it. Starting a test claims one of your workspace's survey numbers; you then send your survey to it.

run = client.surveys.start(
    config_id="d41eaa86-34ae-4b7c-b5cc-debf7c254b44",
    sender="+447921721840",   # the number your survey platform sends FROM
    window_secs=600,          # the survey must arrive within this window
)
print("Send the survey to", run.agent_e164)

# ... trigger your survey platform, aimed at run.agent_e164 ...

result = client.surveys.wait_for_result(run.run_id)   # until the transcript is final
print(result.outcome, result.answers_given)
for turn in result.turns:
    print(turn.at, turn.from_, turn.text)

start raises ConflictError when the workspace has no free survey number for that sender. wait_for_result returns a failed capture rather than raising it; check result.capture.status. Saved respondents live under client.surveys.configs (create, list, get, update, delete), and past results under client.surveys.results (list, list_page, get).

Voices

List the operator-enabled voices a mission test may use, and which one is the default.

voices = client.testing.list_voices()
for voice in voices.voices:
    print(voice.voice_id, voice.name)
print("default:", voices.default_voice_id)

Test configs

client.testing.configs holds reusable scripted test plans. create() takes name, phone_number and steps, plus optional description, type ("standard" or "parameterized"), profile_id, required_parameters, schedule_id, max_duration_secs (30-1800, whole-call ceiling; the tester defaults to 300), carrier_route_id and language. Only the arguments you pass are sent.

config = client.testing.configs.create(
    name="Transfer to agent",
    phone_number="+442012345678",
    max_duration_secs=900,
    steps=[
        {"name": "Main menu", "type": "dtmf", "expected": "Press 1 for sales", "dtmf": "1"},
        {"name": "Hold music", "type": "hold", "durationSecs": 600},
    ],
)

# update() changes only what you pass. schedule_id, max_duration_secs and
# carrier_route_id can also be cleared with None; omit them to leave them as is.
client.testing.configs.update(config.id, description="Nightly", max_duration_secs=None)

Languages

Two different sets, and mixing them up is a 400. Both are exported as Literal aliases, so a typo is a type error rather than a failed run.

TranscriptionLanguage is the language audio is transcribed in. It applies to standard test configs (language=) and to mapping jobs (MappingJobConfig(language=...)), and defaults to en-GB when omitted. English needs its region here - en-GB, en-US, en-AU, en-IN, en-NZ.

CallLanguage is the language a mission-test call is held in (client.mission_tests.create(language=...)). Absent means English. Use "multi" when the IVR may start in English and switch.

from nopaque import CallLanguage, TranscriptionLanguage

# Transcription: a region is required for English.
client.testing.configs.create(
    name="Spanish main menu", phone_number="+34910000000", steps=[], language="es"
)

# Mission-test call language: bare "en" is valid HERE and only here.
client.mission_tests.create(
    sector="insurance",
    mission="Buy a policy",
    acceptance="Bot offers a quote",
    profile={"phone_number": "+34910000000"},
    language="es",
)

The trap: bare en is a valid call language but is rejected as a transcription language, which wants en-GB. Every other call language (multi, es, fr, de, it, pt, nl, hi, ru, ja) appears in both sets, which is what makes it easy to miss. Omitting language on an update leaves the saved value untouched.

The aliases are strict on write, lenient on read. The language= parameters you pass to configs.create(), configs.update() and mission_tests.create() are typed to the Literal, so a typo is caught before a request goes out. The matching fields on the models you read back (TestConfig.language, MissionTestRun.language, MappingJobConfig.language) are plain str, so a row saved with a code this SDK version has never heard of still parses instead of raising. Both aliases are exported either way, for annotating your own variables:

from nopaque import TranscriptionLanguage

preferred: TranscriptionLanguage = "en-GB"

Bring your own carrier

Invite-only. A workspace can dial its tests through its own SIP trunk (a carrier integration, set up with nopaque) instead of nopaque's network. Each trunk has routes that pick a vendor by tech prefix and optional X- headers. Put a route's route_id on a test config as carrier_route_id, and that config's calls go out through the route. If the route can't be dialled when a run starts, the run is refused (ConflictError); the number is never called normally instead.

from nopaque import Nopaque

client = Nopaque()

# Trunks are read-only here; each one lists its routes.
for trunk in client.testing.carrier_integrations.list():
    print(trunk.integration_id, trunk.name, trunk.status, len(trunk.routes))
trunk = client.testing.carrier_integrations.list()[0]

route = client.testing.carrier_integrations.routes.create(
    trunk.integration_id,
    name="Vendor X premium",
    tech_prefix="R7Q2#",  # dialled in front of the number
)
print(route.route_id, route.dialable)

config = client.testing.configs.create(
    name="Main menu via own trunk",
    phone_number="+442012345678",
    steps=[{"name": "Main menu", "type": "dtmf", "expected": "Press 1 for sales", "dtmf": "1"}],
    carrier_route_id=route.route_id,
)

run = client.testing.runs.create(test_config_id=config.id)  # dials config.phone_number
final = client.testing.runs.wait_for_run(run.id)

print(final.outcome, final.carrier)  # carrier: integration_id / route_id dialled
if final.call_telemetry and final.call_telemetry.telephony:
    print("SIP hang-up cause:", final.call_telemetry.telephony.get("sipHangupCause"))
for step in final.step_results:
    print(step.action_type, step.action_value, step.outcome, step.similarity)
    print("  heard:", step.actual_transcript)

Also available: carrier_integrations.get(integration_id), carrier_integrations.routes.list(integration_id), routes.get(integration_id, route_id) and routes.update(integration_id, route_id, name=..., tech_prefix=..., custom_headers=..., enabled=...). enabled=False turns a route off. configs.update(config_id, carrier_route_id=None) removes the route from a config, and the number is dialled normally again.

Features

  • Full coverage of the Nopaque REST API via API-key auth
  • Typed Pydantic v2 models for every request and response
  • Paired sync (Nopaque) and async (AsyncNopaque) clients
  • Automatic pagination for list endpoints
  • Polling helpers for long-running jobs (wait_for_complete)
  • One-call audio upload/download wrapping the presigned-URL flow
  • Method-aware retry with exponential jitter and Retry-After honor
  • Typed exception hierarchy (NotFoundError, RateLimitError, etc.)

Documentation

Full reference: https://nopaque.co.uk/docs/sdks

Authentication

Pass your API key to the client:

Nopaque(api_key="nop_live_...")

Or set the environment variable:

export NOPAQUE_API_KEY=nop_live_...

API keys are workspace-scoped and subject to per-minute rate limits based on your subscription tier. Free tier cannot use API keys.

Requirements

  • Python 3.9+
  • httpx >= 0.27, pydantic >= 2.6

License

MIT

Metadata

Release files for nopaque 0.13.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nopaque 0.13.0
File Size Uploaded Provenance Actions
nopaque-0.13.0.tar.gz 102.8 kB With provenance Details for nopaque-0.13.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for nopaque 0.13.0
File Interpreter ABI Platform Actions
nopaque-0.13.0-py3-none-any.whl Python 3 none any Details for nopaque-0.13.0-py3-none-any.whl

Total release size: 189.2 kB

Release files / nopaque-0.13.0.tar.gz

Download URL nopaque-0.13.0.tar.gz
Size 102.8 kB
Tags Source
SHA-256 checksum
How to use checksums
bf2e9be282a7489196fd06cfe0476e0396e02a687581f8eb2bdd7c1ab078b5fa
BLAKE2b-256 checksum
How to use checksums
e5dfb9d7882cd1a4609da6e4b697dfac0dab14b56ac340f711d7685c7b733e8c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Release files / nopaque-0.13.0-py3-none-any.whl

Download URL nopaque-0.13.0-py3-none-any.whl
Size 86.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
742d931d3c31e67d3b5d221b3f05e3df6441c7713e44d0414052a780f218ce90
BLAKE2b-256 checksum
How to use checksums
5f9594d9a61992235579dd3e4392cce8e6ac1ad3dc986f2e5d5e54767b04ef5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Security for nopaque 0.13.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for nopaque-0.13.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page