Skip to main content

p2d-duck

Unofficial Python client for duck.ai — DuckDuckGo's free, no-account AI chat interface. Supports every active model, streaming, web search, image generation, retry logic, and multi-turn conversation.

pip install p2d-duck

Models

Constant Model ID Effort modes Web search Image gen
gpt5_mini gpt-5.4-mini fast · reasoning yes —
gpt5_nano gpt-5.4-nano fast yes —
claude claude-haiku-4-5 fast · reasoning yes —
mistral mistral-small-2603 fast yes —
gpt_oss tinfoil/gpt-oss-120b fast · reasoning yes —
image_generation image-generation — — yes

Installation

Python 3.10 or later is required.

pip install p2d-duck

Dependencies installed automatically: httpx, py-mini-racer, cryptography, html5lib.


Quick start

from duck_ai import DuckChat, gpt5_mini

with DuckChat(model=gpt5_mini) as chat:
    response = chat.ask("What is the speed of light?")
    print(response)

Usage

Single question

from duck_ai import DuckChat, claude

with DuckChat(model=claude) as chat:
    print(chat.ask("Explain quantum entanglement in one paragraph."))

Multi-turn conversation

from duck_ai import DuckChat, gpt5_mini

with DuckChat(model=gpt5_mini) as chat:
    chat.ask("My name is Ada.")
    print(chat.ask("What is my name?"))

Streaming

from duck_ai import DuckChat, gpt5_mini

with DuckChat(model=gpt5_mini) as chat:
    for token in chat.stream("Write a haiku about rain."):
        print(token, end="", flush=True)
    print()

Reasoning mode

Activates extended chain-of-thought on supported models (gpt5_mini, claude, gpt_oss):

from duck_ai import DuckChat, gpt5_mini

with DuckChat(model=gpt5_mini, effort="reasoning") as chat:
    print(chat.ask("Prove that the square root of 2 is irrational."))

Fast mode

Lower-latency responses with reduced reasoning, available on all models:

from duck_ai import DuckChat, gpt5_nano

with DuckChat(model=gpt5_nano, effort="fast") as chat:
    print(chat.ask("Capital of France?"))

Web search

Injects live DuckDuckGo search results into the context before responding:

from duck_ai import DuckChat, gpt5_mini

with DuckChat(model=gpt5_mini) as chat:
    print(chat.ask("What happened in the news today?", web_search=True))

Image generation

Returns raw image bytes (PNG/JPEG depending on the service response):

from duck_ai import DuckChat, image_generation

with DuckChat(model=image_generation) as chat:
    data = chat.generate_image("a red fox in autumn leaves, oil painting style")
    with open("fox.png", "wb") as f:
        f.write(data)

API reference

DuckChat

DuckChat(
    model: str | Model = gpt5_mini,
    effort: str | None = None,       # None | "fast" | "reasoning"
    max_retries: int = 3,
    timeout: float = 60.0,
)
Method Returns Description
ask(prompt, *, web_search=False) str Blocking single-turn response
stream(prompt, *, web_search=False) Iterator[str] Token-by-token generator
generate_image(prompt) bytes Raw image bytes
reset() None Clear conversation history

Use as a context manager (with DuckChat(...) as chat:) or call .close() when done.

Model constants

from duck_ai import (
    gpt5_mini,         # gpt-5.4-mini        (default)
    gpt5_nano,         # gpt-5.4-nano
    claude,            # claude-haiku-4-5
    mistral,           # mistral-small-2603
    gpt_oss,           # tinfoil/gpt-oss-120b
    image_generation,  # image-generation
)

You can also pass the raw model ID string directly:

DuckChat(model="claude-haiku-4-5")

List all known model IDs at runtime:

from duck_ai.models import list_models
print(list_models())

Retry behaviour

The client automatically retries on transient failures (challenge errors, 5xx server errors). RateLimitError and ConversationLimitError are terminal — they are not retried.

attempt 1 --[ChallengeError]--> re-solve challenge --> attempt 2 --> ...
                                                          max_retries

Disable retries by setting max_retries=1.


Error reference

Exception Condition
DuckChatError Base class for all library exceptions
ChallengeError JS challenge solve failed or was rejected by the server
RateLimitError HTTP 429 — too many requests from this IP
ConversationLimitError Session exceeded duck.ai's per-conversation message cap
APIError Any other non-retryable HTTP error from the server
from duck_ai.exceptions import RateLimitError, ChallengeError

try:
    response = chat.ask("Hello")
except RateLimitError:
    print("Rate limited — wait before retrying or use a different IP.")
except ChallengeError:
    print("Challenge failed — update py-mini-racer or open an issue.")

Command-line interface

python -m duck_ai [--model MODEL] [--effort EFFORT] [--no-stream] [--web]
Flag Description
--model Model name or ID string (default: gpt-5.4-mini)
--effort fast or reasoning (optional)
--no-stream Print the complete response instead of streaming tokens
--web Enable web search on each message
# Interactive reasoning session with Claude
python -m duck_ai --model claude-haiku-4-5 --effort reasoning

# Web-aware session with the default model
python -m duck_ai --web

Architecture

duck.ai requires solving a JavaScript proof-of-work challenge before each session. p2d-duck handles this automatically using py-mini-racer, a V8-based JS runtime, together with a DOM stub layer (stubs.js) that emulates the browser APIs the challenge script depends on.

ask("...")
    │
    ▼
┌─────────────────────────────────────────────────────────┐
│  DuckChat                                               │
│                                                         │
│  1.  GET /duckchat/v1/status                            │──► duck.ai
│      ◄── x-vqd-hash-1  (base64 JS challenge)           │
│                                                         │
│  2.  challenge.solve_challenge(js, user_agent)          │
│       ├─ decode base64 payload                          │
│       ├─ inject DOM stubs  (stubs.js)                   │
│       ├─ evaluate in V8  (py-mini-racer)                │
│       └─ SHA-256 hash result → token                    │
│                                                         │
│  3.  POST /duckchat/v1/chat                             │──► duck.ai
│      x-vqd-hash-1: <solved token>                      │
│      ◄── SSE stream of tokens                           │
│                                                         │
│  4.  assemble and return                                │
└─────────────────────────────────────────────────────────┘
    │
    ▼
response string

The challenge payload is a self-contained obfuscated JS function that fingerprints DOM geometry (getBoundingClientRect, offsetWidth, offsetHeight, getComputedStyle) and navigator properties (webdriver, userAgent). stubs.js supplies realistic values for each property so the challenge computes the same hash a real browser would.


Rate limits

duck.ai enforces per-IP rate limits. If you receive a RateLimitError, wait a few minutes before retrying. Datacenter and cloud IPs are throttled more aggressively than residential IPs.


Requirements

Package Minimum Purpose
httpx 0.27 HTTP/2 client with streaming support
py-mini-racer 0.12 Embedded V8 engine for JS challenge solving
cryptography 42 Token signing and encryption
html5lib 1.1 HTML normalisation used in challenge pre-processing

License

MIT — see LICENSE.


Disclaimer

This project is not affiliated with, endorsed by, or supported by DuckDuckGo. It interfaces with duck.ai's public web UI. DuckDuckGo's terms of service apply. The upstream API may change without notice; open an issue if something breaks.

Release files for p2d-duck 1.3.1

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

Source distribution (sdist)

Source distribution for p2d-duck 1.3.1
File Size Uploaded
p2d_duck-1.3.1.tar.gz 20.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for p2d-duck 1.3.1
File Interpreter ABI Platform
p2d_duck-1.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 42.5 kB

Release files / p2d_duck-1.3.1.tar.gz

Download URL p2d_duck-1.3.1.tar.gz
Size 20.8 kB
Tags Source
SHA-256 checksum
How to use checksums
a41e834842a648885ae9895447d29db181b7ff87081ed308f3e92cb08442dd28
BLAKE2b-256 checksum
How to use checksums
27eea09500cfe200f2818d5c6757eca6faadb3bd515d935a50595ad204b3cf1b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 24, 2026.

Transparency log

Release files / p2d_duck-1.3.1-py3-none-any.whl

Download URL p2d_duck-1.3.1-py3-none-any.whl
Size 21.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
eecd006acfe394caf518a764fab2aa2699a7ed8db9be119089e53149f309e466
BLAKE2b-256 checksum
How to use checksums
297467d69b8c9b01714e637ed51a8539d417926656d2301abf7781da492c302a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.3.1 This release

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.3

2 release files

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