Duck APIduck.ai ↗
Retrieve Duck.ai AI model configurations, capabilities, access tiers, supported tools, and live service status via two simple REST endpoints.
What is the Duck API?
The Duck.ai API exposes 2 endpoints that return structured data about DuckDuckGo's private AI chat service. The get_models endpoint delivers a full list of available AI models — including provider, capabilities, supported tools, file type support, and subscription tier requirements — plus a ready-to-use JavaScript export string. The get_status endpoint reports current service health so you can confirm availability before making downstream calls.
No input parameters required.
curl -X GET 'https://api.parse.bot/scraper/ec7d8282-525d-433a-9943-342788add371/get_models' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace duck-ai-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""
Duck.ai Models API Client
Retrieve AI model data and service status from Duck.ai (DuckDuckGo's private AI chat).
Get your API key from: https://parse.bot/settings
"""
import os
import json
import requests
from typing import Any, Dict, List, Optional
class ParseClient:
"""Client for interacting with Duck.ai Models API via Parse.bot"""
def __init__(self, api_key: Optional[str] = None):
"""
Initialize the Parse API client.
Args:
api_key: API key for authentication. If not provided, reads from PARSE_API_KEY env var.
"""
self.base_url = "https://api.parse.bot"
self.scraper_id = "ec7d8282-525d-433a-9943-342788add371"
self.api_key = api_key or os.getenv("PARSE_API_KEY")
if not self.api_key:
raise ValueError("API key is required. Set PARSE_API_KEY environment variable or pass api_key parameter.")
def _call(self, endpoint: str, method: str = "POST", **params) -> Dict[str, Any]:
"""
Make an API call to the Parse endpoint.
Args:
endpoint: The endpoint name (e.g., 'get_models', 'get_status')
method: HTTP method ('GET' or 'POST')
**params: Additional parameters to pass to the endpoint
Returns:
Response data as a dictionary
Raises:
requests.RequestException: If the API call fails
"""
url = f"{self.base_url}/scraper/{self.scraper_id}/{endpoint}"
headers = {
"X-API-Key": self.api_key,
"Content-Type": "application/json"
}
if method.upper() == "GET":
response = requests.get(url, headers=headers, params=params)
else:
payload = params if params else {}
response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()
return response.json()
def get_models(self) -> Dict[str, Any]:
"""
Fetch all available AI models from Duck.ai.
Returns information about model capabilities, providers, access tiers,
supported tools, and file type support.
Returns:
Dictionary containing:
- models: List of model objects with configurations
- attachment_limits: File/image upload limits per subscription tier
- javascript: Pre-formatted JavaScript export string
- total_models: Count of models returned
"""
return self._call("get_models", method="GET")
def get_status(self) -> Dict[str, Any]:
"""
Fetch the current Duck.ai service status.
Returns:
Dictionary containing:
- status: Primary status code (0 = operational)
- secondaryStatus: Secondary status code
- statusV2: V2 status code
"""
return self._call("get_status", method="GET")
def analyze_model_ecosystem(models: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Analyze the Duck.ai model ecosystem to extract insights.
Args:
models: List of model objects from get_models response
Returns:
Dictionary with ecosystem analysis
"""
analysis = {
"total_models": len(models),
"providers": {},
"access_tiers": {},
"tools_coverage": {},
"image_support": 0,
"pdf_support": 0,
"reasoning_support": 0,
}
for model in models:
# Provider stats
provider = model.get("provider", "unknown").upper()
if provider not in analysis["providers"]:
analysis["providers"][provider] = {
"count": 0,
"models": [],
"max_access_tier": "free"
}
analysis["providers"][provider]["count"] += 1
analysis["providers"][provider]["models"].append(model.get("name"))
# Access tier stats
tiers = model.get("accessTier", [])
for tier in tiers:
if tier not in analysis["access_tiers"]:
analysis["access_tiers"][tier] = 0
analysis["access_tiers"][tier] += 1
# Tools coverage
tools = model.get("supportedTools", [])
for tool in tools:
if tool not in analysis["tools_coverage"]:
analysis["tools_coverage"][tool] = []
analysis["tools_coverage"][tool].append(model.get("name"))
# Feature support
if model.get("supportsImageUpload"):
analysis["image_support"] += 1
file_types = model.get("supportedFileTypes", [])
if "application/pdf" in file_types:
analysis["pdf_support"] += 1
reasoning = model.get("supportedReasoningEffort", [])
if len(reasoning) > 1: # More than just "none"
analysis["reasoning_support"] += 1
return analysis
def filter_models_by_tier(models: List[Dict[str, Any]], tier: str) -> List[Dict[str, Any]]:
"""
Filter models available for a specific access tier.
Args:
models: List of model objects
tier: Access tier ('free', 'plus', 'pro', 'internal')
Returns:
List of models available in the specified tier
"""
return [m for m in models if tier in m.get("accessTier", [])]
def find_best_model_for_task(models: List[Dict[str, Any]],
requires_images: bool = False,
requires_pdf: bool = False,
requires_web_search: bool = False,
tier: str = "pro") -> Optional[Dict[str, Any]]:
"""
Find the best model that matches specific requirements.
Args:
models: List of model objects
requires_images: Whether image upload capability is needed
requires_pdf: Whether PDF file support is needed
requires_web_search: Whether web search tool is needed
tier: Minimum access tier required
Returns:
Best matching model or None if no match found
"""
candidates = filter_models_by_tier(models, tier)
for model in candidates:
if requires_images and not model.get("supportsImageUpload"):
continue
if requires_pdf and "application/pdf" not in model.get("supportedFileTypes", []):
continue
if requires_web_search and "WebSearch" not in model.get("supportedTools", []):
continue
return model
return None
def main():
"""Practical workflow: Check service health, analyze models, and find optimal models for tasks."""
# Initialize the client
client = ParseClient()
print("\n" + "=" * 80)
print("DUCK.AI MODELS ANALYSIS WORKFLOW")
print("=" * 80)
# Step 1: Check service status
print("\n[1/5] Checking Duck.ai service status...")
try:
status_response = client.get_status()
status_code = int(status_response.get("status", -1))
if status_code == 0:
print("✓ Duck.ai service is OPERATIONAL")
else:
print(f"✗ Service degraded - Status code: {status_code}")
print(f" Secondary status: {status_response.get('secondaryStatus', 'unknown')}")
return
except Exception as e:
print(f"✗ Failed to check service status: {e}")
return
# Step 2: Fetch all available models
print("\n[2/5] Fetching available AI models...")
try:
models_response = client.get_models()
models = models_response.get("models", [])
total_models = models_response.get("total_models", 0)
attachment_limits = models_response.get("attachment_limits", {})
print(f"✓ Retrieved {total_models} models from Duck.ai")
except Exception as e:
print(f"✗ Failed to fetch models: {e}")
return
# Step 3: Analyze ecosystem
print("\n[3/5] Analyzing model ecosystem...")
analysis = analyze_model_ecosystem(models)
print("\n Provider Distribution:")
for provider, data in sorted(analysis["providers"].items()):
model_names = ", ".join(data["models"][:2])
suffix = f" (+{len(data['models'])-2} more)" if len(data["models"]) > 2 else ""
print(f" • {provider:15} {data['count']:2} models ({model_names}{suffix})")
print("\n Feature Support:")
print(f" • Models with image upload: {analysis['image_support']}/{total_models}")
print(f" • Models with PDF support: {analysis['pdf_support']}/{total_models}")
print(f" • Models with reasoning effort: {analysis['reasoning_support']}/{total_models}")
print("\n Available Tools:")
for tool, model_names in sorted(analysis["tools_coverage"].items()):
print(f" • {tool:25} {len(model_names)} models")
print("\n Access Tier Distribution:")
for tier, count in sorted(analysis["access_tiers"].items(),
key=lambda x: {"free": 0, "plus": 1, "pro": 2, "internal": 3}.get(x[0], 99)):
print(f" • {tier:10} {count:2} models")
# Step 4: Find models for specific use cases
print("\n[4/5] Finding optimal models for specific use cases...")
use_cases = [
{
"name": "Document Analysis (PDF + Images)",
"requires_pdf": True,
"requires_images": True,
"tier": "pro"
},
{
"name": "Web Research with AI",
"requires_web_search": True,
"tier": "plus"
},
{
"name": "Free Tier Analysis",
"tier": "free"
}
]
for use_case in use_cases:
best_model = find_best_model_for_task(
models,
requires_images=use_case.get("requires_images", False),
requires_pdf=use_case.get("requires_pdf", False),
requires_web_search=use_case.get("requires_web_search", False),
tier=use_case["tier"]
)
status = "✓" if best_model else "✗"
if best_model:
print(f" {status} {use_case['name']:40} → {best_model.get('name')}")
else:
print(f" {status} {use_case['name']:40} → No matching model")
# Step 5: Display subscription tier information
print("\n[5/5] Subscription tier limits...")
tier_order = ["free", "plus", "pro"]
for tier in tier_order:
if tier in attachment_limits:
limits = attachment_limits[tier]
files = limits.get("files", {})
images = limits.get("images", {})
tier_display = tier.upper()
print(f"\n {tier_display} Tier:")
print(f" Files: {files.get('maxPerConversation', '?')} per conversation, "
f"max {files.get('maxFileSizeMB', '?')}MB, "
f"{files.get('maxPagesPerFile', '?')} pages per file")
print(f" Images: {images.get('maxPerTurn', '?')} per turn, "
f"{images.get('maxPerConversation', '?')} per conversation")
# Summary
print("\n" + "=" * 80)
print("WORKFLOW COMPLETE")
print(f"Total models analyzed: {total_models}")
print(f"Providers: {len(analysis['providers'])}")
print(f"Tools available: {len(analysis['tools_coverage'])}")
print("=" * 80 + "\n")
if __name__ == "__main__":
main()Fetches all available AI models from Duck.ai including their capabilities, providers, access tiers, supported tools, and file type support. Returns structured model data along with a pre-formatted JavaScript export string. No parameters required — returns the full model catalog in a single response.
No input parameters required.
{
"type": "object",
"fields": {
"models": "array of model objects with id, provider, name, capabilities, and access tier info",
"javascript": "string containing the model data formatted as JavaScript export statements",
"total_models": "integer count of models returned",
"attachment_limits": "object with file and image upload limits per subscription tier (free, plus, pro)"
},
"sample": {
"data": {
"models": [
{
"id": "gpt-5.4",
"name": "GPT-5.4",
"provider": "openai",
"modelName": "GPT-5.4",
"settingId": "261",
"accessTier": [
"internal",
"plus",
"pro"
],
"modelVariant": null,
"modelShortName": "GPT-5.4",
"supportedTools": [
"WebSearch",
"SearchResults",
"RelatedSearchTerms"
],
"entityHasAccess": false,
"supportedFileTypes": [
"application/pdf"
],
"supportsImageUpload": true,
"reasoningEffortAccess": [
{
"id": "none",
"accessTier": [
"internal",
"plus",
"pro"
],
"entityHasAccess": false
}
],
"supportedReasoningEffort": [
"none",
"low",
"medium"
]
}
],
"javascript": "const duckAIModels = [...];\n\nconst attachmentLimits = {...};\n\nexport { duckAIModels, attachmentLimits };\n",
"total_models": 8,
"attachment_limits": {
"pro": {
"files": {
"maxFileSizeMB": 25,
"maxPagesPerFile": 50,
"maxPerConversation": 5,
"maxTotalFileSizeBytes": 26214400
},
"images": {
"maxPerTurn": 3,
"maxPerConversation": 10,
"maxInputCharsWithAttachments": 4500
}
},
"free": {
"files": {
"maxFileSizeMB": 5,
"maxPagesPerFile": 15,
"maxPerConversation": 3,
"maxTotalFileSizeBytes": 5242880
},
"images": {
"maxPerTurn": 3,
"maxPerConversation": 5,
"maxInputCharsWithAttachments": 4500
}
},
"plus": {
"files": {
"maxFileSizeMB": 25,
"maxPagesPerFile": 35,
"maxPerConversation": 5,
"maxTotalFileSizeBytes": 26214400
},
"images": {
"maxPerTurn": 3,
"maxPerConversation": 10,
"maxInputCharsWithAttachments": 4500
}
}
}
},
"status": "success"
}
}About the Duck API
Model Data
The get_models endpoint returns an array of model objects, each containing fields such as id, provider, name, capabilities, and access tier information indicating whether a model is available on free, plus, or pro plans. The response also includes attachment_limits, an object that maps each subscription tier (free, plus, pro) to its file and image upload constraints. A total_models integer gives you a quick count without iterating the array. Alongside the structured data, the endpoint returns a javascript string — a pre-formatted export statement you can drop directly into a JS/TS project.
Service Status
The get_status endpoint returns three fields: status (a string code where "0" indicates fully operational), statusV2 (an integer version of the same signal), and secondaryStatus (a secondary string code for more granular health information). This is useful for uptime checks, conditional logic before triggering AI requests, or surfacing degraded-state warnings in a UI.
What the API Covers
Both endpoints require no input parameters — all responses are returned in full. The model data reflects the live Duck.ai catalog, including which models are gated behind paid tiers and what file types each model can accept as attachments. There is no filtering or pagination; you receive the complete dataset on every call.
The Duck API is a managed, monitored endpoint for duck.ai — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when duck.ai changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official duck.ai API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Build a model-picker UI that filters Duck.ai models by access tier (free, plus, or pro) using the
capabilitiesand tier fields. - Display attachment constraints to users before they upload files, using the
attachment_limitsobject keyed by subscription tier. - Run an uptime monitor that polls
get_statusand alerts when thestatusfield is non-zero. - Auto-generate TypeScript type definitions for Duck.ai models using the
javascriptexport string returned byget_models. - Track changes in the Duck.ai model catalog over time by recording
total_modelsand modelidlists on a schedule. - Gate AI chat features in your app behind a live availability check using
statusV2fromget_status.
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 100 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does Duck.ai have an official public developer API?+
What does the `get_models` endpoint return beyond a list of model names?+
id, provider, name, capabilities, and access tier details showing which subscription plans can use it. The response also includes attachment_limits (per-tier file and image upload constraints) and a javascript field containing the full model dataset as a pre-formatted JS export string.Does the API expose individual chat completions or message history from Duck.ai?+
How granular is the service status data?+
get_status endpoint returns three fields: a status string ("0" = operational), a statusV2 integer, and a secondaryStatus string. It does not break down status by individual model or geographic region. You can fork the API on Parse and revise it to add more granular health endpoints if needed.Can I filter `get_models` results to a specific provider or tier?+
provider and access tier fields in each model object. You can fork this API on Parse and revise it to add query parameters that pre-filter by provider or tier.