pip install youdotcomRequires Python 3.10+. Also available via uv add youdotcom or poetry add youdotcom.
Get an API key from you.com/platform and set it as YDC_API_KEY.
import os
from youdotcom import You
with You(api_key_auth=os.getenv("YDC_API_KEY"), timeout_ms=60_000) as you:
res = you.answer(query="What caused the 2008 financial crisis?")
print(res.answer)That prints a markdown answer with inline [[1, 2]] citations. The sources behind
them are on the response:
for citation in res.citations or []:
print(citation.source, citation.excerpts)Two things about that snippet worth knowing up front:
timeout_msis doing real work. Without it, requests inherit httpx's 5 second default, andanswertakes longer than that. See Timeouts.The key is explicit here, but it doesn't have to be. Pass
api_key_auth=None, or omit it, to readYDC_API_KEYfrom the environment. See Authentication for the resolution order.
Every method is a direct call on You, and every one has an _async twin with
the same signature.
search() and answer() normalize their enum-typed parameters, so plain
strings work in any case. country="us" and safesearch="STRICT" are both
accepted. Elsewhere, pass the value as the API spells it (all lowercase) or
import the enum from youdotcom.models.
A synthesized answer with citations, grounded in live web results.
res = you.answer(
query="What are the tradeoffs of vector vs. keyword search?",
freshness="month",
include_domains=["arxiv.org"],
)
res.answer # markdown, with inline [[n]] citations
res.citations # [AnswerCitation(source, excerpts)]
res.results.web # results used during synthesisRanked web and news results.
res = you.search(
query="EU AI Act enforcement timeline",
count=10,
country="us",
freshness="week",
)
for hit in res.results.web or []:
print(hit.title, hit.url)include_domains restricts results to an allowlist; exclude_domains and
boost_domains filter and re-rank. include_domains cannot be combined with
either of the others. The API returns 422 if you try. Search also supports
search operators.
Clean HTML or Markdown for a list of URLs.
pages = you.contents(
urls=["https://example.com", "https://you.com"],
formats=["markdown", "metadata"],
)
for page in pages:
print(page.url, page.title)
print(page.markdown)formats accepts html, markdown, and metadata (JSON-LD and OpenGraph).
Use max_age to reject cached content older than a given number of seconds.
Multi-step research with reasoning and cited sources. Higher effort levels run more searches and take longer.
res = you.research(
input="Compare the unit economics of the major cloud providers",
research_effort="deep", # lite | standard | deep | exhaustive | frontier
)
print(res.output.content)
for source in res.output.sources or []:
print(source.url)you.finance_research() is the finance-tuned counterpart, taking
research_effort of deep or exhaustive.
Deep and exhaustive runs can take minutes, and frontier runs far longer. For
anything beyond standard, use background mode.
Every method has an _async variant. Use async with so both transports are
released on exit.
import asyncio
import os
from youdotcom import You
async def main():
async with You(api_key_auth=os.getenv("YDC_API_KEY")) as you:
res = await you.answer_async(query="What is retrieval-augmented generation?")
print(res.answer)
asyncio.run(main())Concurrent calls share the one client:
answer, results = await asyncio.gather(
you.answer_async(query="What is RAG?"),
you.search_async(query="RAG benchmarks", count=5),
)Rather than holding a request open for minutes, background mode submits the task
and returns immediately. The helpers in youdotcom.research_helpers cover the
common shapes.
Submit and wait. Handles submission, streaming, and the final fetch:
from youdotcom.research_helpers import research_and_wait
detail = research_and_wait(
you,
input="Survey the state of solid-state battery commercialization",
research_effort="exhaustive",
)
print(detail.status, detail.result)The wait is bounded automatically: 10 minutes for standard, deep, and
exhaustive, 4 hours for frontier. Pass timeout_s to override. It raises
TimeoutError if no terminal event arrives, and RuntimeError if the task ends
in a non-completed state.
Submit and poll, if you'd rather own the loop:
from youdotcom.research_helpers import research_background, poll_research_task
task = research_background(you, input="...", research_effort="deep")
detail = poll_research_task(you, task.task_id, interval_s=5.0)Stream events as the task progresses:
from youdotcom.research_helpers import stream_research
for evt in stream_research(you, task_id=task.task_id):
print(evt.event, evt.data)
if evt.event in ("response.done", "completed", "error", "failed", "cancelled"):
breakstream_research() tolerates event names outside the documented set, yielding
them as raw dicts. Prefer it over you.stream_research_task(), which validates
strictly and will raise on an unrecognized event. Pass from_id to resume a
stream after a disconnect.
Each helper has an _async twin: research_and_wait_async,
research_background_async, poll_research_task_async, stream_research_async.
The API key is sent as the X-API-Key header. How it's resolved:
api_key_auth |
Behavior |
|---|---|
omitted, or None |
Reads YDC_API_KEY, then the legacy YOU_API_KEY_AUTH |
| a non-empty string, or a callable returning one | That key is used; no environment lookup |
"" or blank, or a callable returning an empty string |
Raises ValueError |
Every endpoint requires a key, so an empty string is never valid. It means a key was expected and none arrived. The SDK raises rather than reading the environment, since falling back would run the request under whatever identity the environment happens to hold instead of the one the code asked for.
In practice that shows up as os.getenv("YDC_API_KEY", "") with the variable
unset. Use os.getenv("YDC_API_KEY"). None is how you ask for the lookup.
A callable is resolved on each request, so it can return a rotating key.
Every API error subclasses YouError, which carries .message,
.status_code, .body, .headers, and .raw_response. The typed subclasses
below add a parsed .data.
from youdotcom.errors import (
PaymentRequiredResponseError,
UnauthorizedResponseError,
YouError,
)
try:
res = you.answer(query="...")
except UnauthorizedResponseError:
... # 401, bad or missing key
except PaymentRequiredResponseError as e:
print(e.data.message, e.data.upgrade_url) # 402, out of credits
except YouError as e:
print(e.status_code, e.body) # anything else from the APIAnswer and search share one set of error classes; research, finance research,
contents, and the task endpoints each raise their own, so you can catch a 422
from research without catching one from search.
| Status | Answer / Search | Contents | Research | Finance Research | Task get / stream |
|---|---|---|---|---|---|
| 401 | UnauthorizedResponseError |
ContentsUnauthorizedError |
ResearchUnauthorizedError |
FinanceResearchUnauthorizedError |
GetResearchTask… / StreamResearchTask…UnauthorizedError |
| 402 | PaymentRequiredResponseError answer only |
n/a | n/a | n/a | n/a |
| 403 | ForbiddenResponseError |
ContentsForbiddenError |
ResearchForbiddenError |
FinanceResearchForbiddenError |
…ForbiddenError |
| 404 | n/a | n/a | n/a | n/a | …NotFoundError |
| 422 | UnprocessableEntityResponseError |
n/a | ResearchUnprocessableEntityError |
FinanceResearchUnprocessableEntityError |
n/a |
| 500 | InternalServerErrorResponse |
ContentsInternalServerError |
ResearchInternalServerError |
FinanceResearchInternalServerError |
…InternalServerError |
Two errors sit outside that table: ResponseValidationError when a response
doesn't match its model, and httpx.RequestError (and subclasses) for transport
failures such as connection resets and timeouts.
The SDK does not retry by default. Opt in per call or for the whole client:
from youdotcom.utils import BackoffStrategy, RetryConfig
retries = RetryConfig(
"backoff",
BackoffStrategy(initial_interval=500, max_interval=10_000, exponent=1.5, max_elapsed_time=60_000),
retry_connection_errors=True,
)
with You(api_key_auth=key, retry_config=retries) as you: # whole client
res = you.search(query="...", retries=retries) # or one callRetries apply to 429, 500, 502, 503, and 504.
Set one. With no timeout_ms, requests inherit the underlying httpx client's
default of 5 seconds, which is far too short for answer, research, and
finance_research. Those endpoints routinely take tens of seconds, so a call
without a timeout will raise httpx.ReadTimeout before the API responds.
timeout_ms applies to the whole client or to a single call:
with You(api_key_auth=key, timeout_ms=60_000) as you:
answer = you.answer(query="...") # inherits 60s
results = you.search(query="...", timeout_ms=10_000) # this call onlySearch and contents are fast enough for the default. Research in background mode
is the exception: the helpers under
Long-running research manage their own deadlines, so
timeout_s there bounds the wait rather than timeout_ms.
search and contents go to https://ydc-index.io. Everything else goes to
https://api.you.com: answer, research, finance_research, and the research
task endpoints. The SDK routes each call for you. To point one call
elsewhere, at a proxy or a test server, pass server_url to the method:
res = you.search(query="...", server_url="http://localhost:18080")The constructor's server_url sets the default host, which affects the
api.you.com endpoints. Because search and contents have their own
per-operation default, they are unaffected by it; override those per call.
Pass any httpx.Client / httpx.AsyncClient to control proxies, TLS, custom
headers, or connection limits:
import httpx
http_client = httpx.Client(proxy="http://localhost:8030", headers={"x-team": "search"})
with You(api_key_auth=key, client=http_client) as you:
...
http_client.close() # a transport you supply is yours to closeYou can also pass anything satisfying the HttpClient / AsyncHttpClient
protocols in youdotcom.httpclient to wrap requests with your own logic. Note
that a transport you supply is yours to close. The SDK only closes the ones it
creates.
You holds open connections and has no public close(), so use it as a context
manager. Both transports are released on exit.
with You(api_key_auth=key) as you:
...
# or: async with You(api_key_auth=key) as you:An instance is not reusable after the block exits, including for calls of the other flavor. Use one instance per sync/async flavor.
Set YOU_DEBUG=1 for request and response logging, or pass your own logger:
import logging
with You(api_key_auth=key, debug_logger=logging.getLogger("youdotcom")) as you:
...Authorization, X-API-Key, Cookie, and Set-Cookie are redacted. Request
and response bodies are not, and may carry sensitive data. Don't enable debug
logging in production, and don't commit debug logs to version control.
- API reference: endpoints, parameters, response schemas
- Quickstart
- Pricing and plans
docs/: per-method SDK reference generated from this codebaseexamples/: runnable, typed examples for every endpoint- MIGRATION.md: upgrading between major versions
./scripts/run_tests.shStarts the Go mock server, sets up a virtualenv, runs the suite, and cleans up.
Pass --cleanup to remove the virtualenv afterwards. See
tests/README.md for running pieces of it directly.
This SDK is hand-maintained rather than generated, so scripts/check_drift.py
enforces what code generation used to guarantee: it diffs the published OpenAPI
specs against the SDK surface (endpoints, server URLs, enum values, request
parameters, response fields) and runs on every PR plus weekly.
python scripts/check_drift.py --verboseThis project follows Semantic Versioning. Breaking changes only land in major releases and are documented in MIGRATION.md and CHANGELOG.md.
Pull requests are welcome. See CONTRIBUTING.md for setup and guidelines. For bugs and feature requests, open an issue.
MIT. See LICENSE.
