OpenAI-compatible Β· Playwright-driven

An OpenAI-compatible API for Duck.ai β€” driven by a real browser.

duckai-api opens Duck.ai in a real Chromium session via Playwright, types your prompt into the composer, streams the answer back token by token, and re-exposes it as a standard /v1/chat/completions endpoint. Any OpenAI SDK, Chatbox, Cherry Studio, Open WebUI or Cursor client can talk to it with nothing but a base URL.

🐍 Python 3.11+ ⚑ Streaming SSE πŸ–₯️ Built-in web GUI πŸ”‘ Optional API key πŸ›‘οΈ Hardened 🐳 Docker ready
Overview

What it is

Duck.ai publishes no public API. This project drives the ordinary web interface in a real browser and repackages the stream as an OpenAI-compatible surface.

πŸ”Œ

Drop-in compatibility

Speaks the standard /v1/chat/completions and /v1/models contract β€” streaming and non-streaming.

🎯

Real token streaming

Intercepts Duck.ai's SSE feed and forwards deltas as OpenAI chunks with zero buffering.

🧠

Multiple models

GPT-6 Luna, Claude Haiku 4.5, GPT-5.4 mini, Mistral Small 4, GPT-OSS 120B and Gemma 4 31B β€” with friendly aliases.

πŸ–₯️

Bundled web GUI

A dark ChatGPT-style interface at /, with an optional API-key field and live model picker.

♻️

Memory recycling

The browser page is recycled automatically after N requests or a time interval to keep long runs healthy.

πŸ”’

Hardened by default

Optional API key, constant-time comparison, compliant CORS, generation-scoped stream routing and disconnect handling.

Quick start

Running it locally

Python 3.11 or newer (the bridge uses asyncio.timeout).

  1. Install dependencies

    Create a virtual environment, then install the Python packages and the Chromium build Playwright needs.

    terminal
    # 1. install packages
    pip install -r requirements.txt
    
    # 2. download the Chromium binary
    playwright install chromium
  2. Start the server

    Defaults to http://localhost:3006. The web GUI is at /, the schema at /docs.

    terminal
    python server.py
  3. Point any OpenAI client at it

    Use http://localhost:3006/v1 as the base URL. If API_KEY is unset, any key value is accepted.

    python
    from openai import OpenAI
    
    client = OpenAI(base_url="http://localhost:3006/v1", api_key="duckai")
    
    stream = client.chat.completions.create(
        model="gpt-6-luna",
        messages=[{"role": "user", "content": "Explain quantum computing in one sentence."}],
        stream=True,
    )
    for chunk in stream:
        print(chunk.choices[0].delta.content or "", end="", flush=True)
Reference

API endpoints

All endpoints live under the same host and port as the GUI.

MethodPathPurposeAuth
POST/v1/chat/completionsChat completion (streaming & non-streaming)if key set
GET/v1/modelsList available modelsif key set
GET/healthHealth & bridge statusopen
GET/Web chat GUIopen
GET/docsOpenAPI schema (Swagger UI)open

Streaming a completion

curl
curl -N -X POST http://localhost:3006/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "model": "gpt-6-luna",
    "messages": [{"role": "user", "content": "Hello!"}],
    "stream": true
  }'

Tip: pass "stream": false to receive a single JSON chat.completion object instead of SSE chunks. Model names are validated β€” an unknown model returns HTTP 400.

Models

Available models

Aliases on the right are accepted too and resolved to the canonical model.

Model IDDescriptionAliases
gpt-6-lunaFlagship β€” best for everyday usegpt-6, luna, default
claude-haiku-4.5Fast, concise reasoningclaude-haiku, claude-3-5-haiku, claude
gpt-5.4-miniLightweight and fastgpt-4o-mini, gpt-4o, gpt-3.5-turbo
mistral-small-4Efficient multilingualmistral
gpt-oss-120bOpen-weights large modeloss-120b
gemma-4-31bGoogle open model (beta)gemma
Configuration

Environment variables

Everything is optional β€” sensible defaults ship in the Docker image.

VariableDefaultDescription
PORT3006HTTP port
HOST0.0.0.0Bind host
HEADLESStrueSet false to show the browser window
API_KEYunsetIf set, every /v1/* request must send Authorization: Bearer <key>
RECYCLE_AFTER_REQUESTS100Recycle the browser page after this many requests
RECYCLE_INTERVAL_HOURS24Recycle the browser page after this many hours

Docker

terminal
# build & run with compose
docker compose up --build

# …or plain docker
docker build -t duckai-api .
docker run -p 3006:3006 -e API_KEY="your-secret" duckai-api

The image is based on python:3.11-slim and ships Chromium plus its system libraries, so no host browser is required.

Security

Hardening notes

This project was audited adversarially; the findings below have been addressed in the current revision.

Generation-scoped routing

Every stream frame is tagged with a generation id, so a superseded request can never leak tokens into the next one.

Sanitized rendering

Model output is rendered through DOMPurify after markdown parsing, closing the stored-XSS path in the GUI.

Constant-time auth

API keys are compared with hmac.compare_digest over the Authorization header only.

Bounded lock acquisition

A cancel-safe 30-second lock acquisition prevents a stalled request from blocking the bridge forever.

Disconnect handling

Client disconnects stop the browser generation instead of letting it run on in the background.

Error propagation

Upstream Duck.ai error frames surface as stream errors rather than empty "successful" responses.

Still your responsibility: when API_KEY is unset the API is open, and /health, /docs and the GUI remain unauthenticated. Bind to a trusted network, or set a key and put it behind a reverse proxy.

⚠️ Disclaimer & legal notice

This project is unofficial and is not affiliated with, endorsed by, or sponsored by Duck.ai or DuckDuckGo. It is an independent tool built for educational, research and interoperability purposes.

Duck.ai publishes no public API. This software works by automating the ordinary web interface in a real browser β€” it types into the composer and reads the page. Automating a consumer service is exactly the kind of activity that terms of service commonly prohibit.

  • You are responsible for compliance. Whether you may use this is between you and Duck.ai, and the law where you live. If using it would violate Duck.ai's Terms of Service, do not use it.
  • No warranty. The software is provided "as is", without warranty of any kind. Models, DOM selectors and upstream behaviour change without notice and may break at any time.
  • Use at your own risk. Traffic originates from your machine or server IP; misusing the relay can result in rate limits, account or IP bans, and other consequences.
  • No secrets shipped. Never commit real API keys. Treat any key you configure as a credential and keep the service off the public internet unless you understand the exposure.