> ## Agent Instructions > Seltz is the web knowledge layer for AI agents: web search, cited answers, multi-step research, page fetching, and recurring monitors, served from Seltz's own index rather than a live scrape. > Use Seltz when a task needs current, citable, public web content, not when your own knowledge already answers it confidently. Seltz has no access to a caller's private or authenticated data. > Choose which endpoint to use according to the job: Use Answer for one synthesized, cited response; Use Search for the raw documents to reason over yourself; Use Agent for in-depth research needing several rounds of searching and following up, not a single lookup; Use Fetch when you already have a URL and just need its content, not a search; Use Monitor for an ongoing feed of what's new, not a one-off query or near-real-time alerting. > When you use Search, Answer, or Monitor, set a `scope` that matches the query. `web` (beta) is the default and covers general queries. Only these three endpoints take a scope. See https://docs.seltz.ai/data/data-concepts#scopes for the full list of scopes and what each one covers. > For the REST API, send your API key in the `x-api-key` header. The Python and JavaScript SDKs read the `SELTZ_API_KEY` environment variable and handle authentication for you. To use Seltz as MCP tools, connect `https://mcp.seltz.ai/mcp`. > The OpenAPI 3.1 spec for the REST API is at https://docs.seltz.ai/api-reference/openapi.json. Point function-calling or client-generation tooling at that file. > Use `/llms-full.txt` for the full text of every page. # Concepts Source: https://docs.seltz.ai/agent/concepts Understanding runs, output, and citations in the Seltz Agent API ## Overview **Agent** turns a question into a multi-step research loop: it plans its own searches, follows up on what it finds, and keeps going until it has enough to answer, rather than the single grounding search [Answer](/answer/answer-concepts) makes. Because that loop can take a while, a run is asynchronous rather than a single request/response. This topic explains the terminology used in Agent and how the feature works. ## The run lifecycle A run moves through a small, closed state machine: ``` pending → running → completed / failed / cancelled ``` [`create`](/api-reference/agent/start-run) returns at once with the run in `pending` state. There is no notification when it finishes. Poll [`get`](/api-reference/agent/get-run) by id, or let the SDKs' `wait` / `create_and_wait` (`createAndWait`) do the polling for you. Runs typically finish in under a minute for a simple question. A harder one can take several minutes and up to around an hour. A higher [`effort`](#effort-levels) raises how far a run can go before it stops. [`status`](/api-reference/agent/get-run#response-status) is the coarse outcome; [`stop_reason`](/api-reference/agent/get-run#response-stop-reason-one-of-1) is the fine-grained one, and it appears only once a run reaches a terminal state: | `status` | Legal `stop_reason` values | Meaning | | - | - | - | | `completed` | `finished`, `budget_reached`, `timeout` | Usable output, whether or not the run ran to full completion. | | `failed` | `budget_reached`, `timeout`, `invalid_output`, `internal_error` | No usable output. | | `cancelled` | `cancelled` | Stopped on request; a cancelled run has no output. | `budget_reached` and `timeout` are cut-offs, not verdicts on their own: a budget-capped run with schema-valid partial output is `completed` + `budget_reached`; one with nothing usable is `failed` + `budget_reached`. See [Reference](/agent/reference#run-status-and-stop-reason) for the full table. ## Output: text or structured By default, [`output.text`](/api-reference/agent/start-run#response-output-one-of-1) is a cited Markdown report: inline `[n]` markers point at entries in [`output.sources`](/api-reference/agent/start-run#response-output-one-of-1), the list of pages the run consulted. Pass [`output_schema`](/api-reference/agent/start-run#body-output-schema-one-of-0) (the same OpenAI-style `response_format` object `answer` and chat completions accept) to also get [`output.structured`](/api-reference/agent/start-run#response-output-one-of-1), a JSON result shaped by your schema. Requesting structured output does not replace the text report; both are present on a completed run. See [Structured output](/agent/guides/structured-output). ## Effort levels Pass [`effort`](/agent/reference#request) to control how much research depth a run applies: `low`, `medium`, `high`, or `max`. Omit it and a run executes at `medium`. A higher effort level costs more. How much extra time or depth it actually buys varies by question: a straightforward query can complete just as fast at `max` as at `low`. Effort levels set a ceiling on how far a run can go, not a guarantee it will use it. An unrecognized value is rejected with `INVALID_EFFORT` before the run starts. See [Reference](/agent/reference#errors). ## Sources, grounding, and citations Three related lists carry attribution, at two different levels of the output: * **`output.sources`**: every page the run consulted, in retrieval order. Each entry has a stable `id` (starting at 1) and a `url`. This is the list `output.text`'s inline `[n]` markers point into. * **[`output.grounding`](/api-reference/agent/start-run#response-output-one-of-1)**: present only alongside `output.structured`. One entry per field of the structured result the run could ground, keyed by a dot-notation `field` path (e.g. `"acquisitions.0.acquirer"`). * Each grounding entry's **`citations`** point back into `output.sources` by `source_id`, so a field can cite more than one source. A field the run could not ground is `null` in `output.structured`; the model does not invent a value, and the field has no corresponding `output.grounding` entry. ## Cancellation is best-effort [`cancel`](/api-reference/agent/cancel-run) requests that a run stop; it is not a guarantee. It cancels a `pending` run at once, but a `running` one stops at its **next step**. If the run is close enough to finishing, it can complete before the cancellation takes effect, so `cancel` returns whatever the run's status actually is at that moment rather than forcing it to `cancelled`. This makes `cancel` safe to call speculatively: it never errors on a run that already ended, so you can retry it and race it against completion. ## Next steps To learn more about Seltz Agent, check out the following topics: * [Quickstart](/agent/quickstart): making your first request * [Basic run](/agent/guides/basic-run): the full request/response cycle, including the cURL poll * [Structured output](/agent/guides/structured-output): requesting a JSON result instead of Markdown * [List runs](/agent/guides/list-runs): paging this org's runs, newest first * [Poll for completion](/agent/guides/poll-for-completion): managing the wait yourself * [Cancel a run](/agent/guides/cancel-a-run): best-effort cancellation, and why it can lose the race to completion * [Handle errors](/agent/guides/handle-errors): rejected requests versus failed runs * [Reference](/agent/reference): endpoints, fields, limits and errors * [Agent API Reference](/api-reference/agent/start-run): full REST request and response specification # Basic run Source: https://docs.seltz.ai/agent/guides/basic-run The full request/response cycle for a single run against the Seltz Agent API, in each language To get an in-depth, cited answer to a question, start a run with the query, wait for it to finish, and read the result. This guide demonstrates that workflow in full. The [quickstart](/agent/quickstart) shows the shortest path to a result. This guide walks the same request through in more detail, including what the SDKs' `create_and_wait` / `createAndWait` hide from you, and the manual poll a plain REST caller has to do instead. ## 1. Start a run `create_and_wait` / `createAndWait` call [`create`](/api-reference/agent/start-run), then poll until the run finishes, and hand back the completed envelope. cURL has no waiter, so the same request only starts the run. This example also sets [`effort`](/agent/concepts#effort-levels) to `"high"` for deeper research than the default `medium`: ```python Python theme={null} from seltz import Seltz client = Seltz(api_key="your-api-key") run = client.agent.create_and_wait( "What are the three most notable AI-company acquisitions announced in 2026?", effort="high", ) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz({ apiKey: "your-api-key" }); let run = await client.agent.createAndWait({ query: "What are the three most notable AI-company acquisitions announced in 2026?", effort: "high", }); ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/agent/runs \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "What are the three most notable AI-company acquisitions announced in 2026?", "effort": "high"}' ``` The cURL call returns at once with the run in `pending` state. [`output`](/api-reference/agent/start-run#response-output-one-of-1) is present but its fields stay unset until the run reaches a terminal state: ```json theme={null} { "id": "run_01a091ddea6876b390d54cc3f830cd50", "object": "agent.run", "status": "pending", "stop_reason": null, "created_at": "2026-09-11T19:07:14Z", "started_at": null, "completed_at": null, "request": { "query": "What are the three most notable AI-company acquisitions announced in 2026?", "output_schema": null, "effort": "high" }, "output": { "text": null, "structured": null, "sources": [], "grounding": [] } } ``` ## 2. Poll for the result Retrieve the run by the `id` the create call returned. In Python and TypeScript, `create_and_wait` / `createAndWait` already did this polling for you above, so `run` there is already the finished envelope; [`get`](/api-reference/agent/get-run) is the single check it repeats under the hood, useful on its own when you want to look a run up independently instead of holding onto the object `create` returned. Over cURL, this is the only way to find out whether the run is done: ```python Python theme={null} run = client.agent.get(run.id) ``` ```typescript TypeScript theme={null} run = await client.agent.get(run.id); ``` ```bash cURL theme={null} RUN_ID="your-run-id" # the id the create response returned curl "https://api.seltz.ai/v1/agent/runs/$RUN_ID" \ -H "x-api-key: $SELTZ_API_KEY" ``` See [Poll for completion](/agent/guides/poll-for-completion) for the full loop that turns one check into "wait until done," and for `wait`'s options. Check [`status`](/api-reference/agent/get-run#response-status) before reading anything from `output`. A run that hasn't finished yet comes back as `running` (or `pending`), not an error, and `output`'s fields stay unset, the same status as right after the run is created: ```json theme={null} { "id": "run_01a091ddea6876b390d54cc3f830cd50", "object": "agent.run", "status": "running", "stop_reason": null, "created_at": "2026-09-11T19:07:14Z", "started_at": "2026-09-11T19:07:15Z", "completed_at": null, "request": { "query": "What are the three most notable AI-company acquisitions announced in 2026?", "output_schema": null, "effort": "high" }, "output": { "text": null, "structured": null, "sources": [], "grounding": [] } } ``` [`started_at`](/api-reference/agent/get-run#response-started-at-one-of-0) distinguishes `running` from `pending`: it is `null` while a run waits for a worker, and set the moment one picks up the run. Keep polling until `status` is `completed`, `failed`, or `cancelled`, or use `wait` / `createAndWait`, which does exactly this loop for you. Once the status is `completed`, a completed run's [`output.text`](/api-reference/agent/get-run#response-output-one-of-1) is a cited Markdown report, with inline `[n]` markers pointing at [`output.sources`](/api-reference/agent/get-run#response-output-one-of-1): ```json theme={null} { "id": "run_01a091ddea6876b390d54cc3f830cd50", "object": "agent.run", "status": "completed", "stop_reason": "finished", "created_at": "2026-09-11T19:07:14Z", "started_at": "2026-09-11T19:07:15Z", "completed_at": "2026-09-11T19:07:25Z", "request": { "query": "What are the three most notable AI-company acquisitions announced in 2026?", "output_schema": null, "effort": "high" }, "output": { "text": "## Three most notable AI-company acquisitions announced in 2026\n\nThere is no objective league table for “most notable,” so I ranked these by reported transaction value, strategic significance, and likely industry impact. As of **September 11, 2026**, the strongest candidates are:\n\n1. **SpaceX → Anysphere (Cursor): approximately $60 billion** — announced June 16. The all-stock transaction would make Anysphere, the company behind the AI coding assistant Cursor, a wholly owned SpaceX subsidiary. Cursor was reported to have about **$2.6 billion in annualized B2B revenue**, making this both the largest deal identified in the retrieved results and a major expansion of SpaceX into enterprise AI software and developer tools. [1]\n\n2. **Google → Wiz: $32 billion** — announced in 2025 but **closed in March 2026**, so it is not technically a 2026 announcement. It is nevertheless one of the year’s most consequential AI-adjacent acquisitions: Google’s largest-ever deal brings cloud-security capabilities into Google Cloud as customers increasingly adopt AI and attackers use AI more aggressively. [2]\n\n3. **Nvidia → Hugging Face: approximately $12.9 billion** — reported/agreed in late August and covered in September. The purchase would give Nvidia control of a major platform for distributing, testing, and collaborating on open AI models, datasets, and software. Strategically, it extends Nvidia beyond chips into the developer and open-model ecosystem, while helping it hedge against customers developing their own processors. [3][4]\n\n### Important qualification\nIf “announced in 2026” is interpreted strictly, **Google–Wiz should be excluded**, because the retrieved source says Google announced the intended purchase the previous year; only the closing occurred in 2026. On that strict basis, the retrieved material does not establish a comparably large third deal with enough reliable detail. The Nvidia–Hugging Face transaction is also described in some results as a report or proposed deal rather than a fully documented completed acquisition. [2][4]", "structured": null, "sources": [ { "id": 1, "url": "https://www.latestly.com/technology/spacex-to-acquire-ai-coding-startup-cursor-in-usd-60-billion-deal-7475967.html" }, { "id": 2, "url": "https://www.businessinsider.com/google-closes-wiz-acquisition-cybersecurity-2026-3" }, { "id": 3, "url": "https://www.pymnts.com/cpi-posts/nvidia-to-buy-hugging-face-for-12-9-billion-in-open-ai-push/" }, { "id": 4, "url": "https://indiantelevision.com/mam/nvidia-to-acquire-ai-startup-hugging-face-for-12-9-billion-report/" } ], "grounding": [] } } ``` ## 3. Read the result In Python and TypeScript, `create_and_wait` / `createAndWait` already returned this same envelope, so there is no separate call. Read `run.output.text` and walk `run.output.sources` for the citation list: ```python Python theme={null} print(run.output.text) for source in run.output.sources: print(f"[{source.id}] {source.url}") ``` ```typescript TypeScript theme={null} console.log(run.output?.text); for (const source of run.output?.sources ?? []) { console.log(`[${source.id}] ${source.url}`); } ``` ```text Example output theme={null} ## Three most notable AI-company acquisitions announced in 2026 There is no objective league table for "most notable," so I ranked these by reported transaction value, strategic significance, and likely industry impact. As of **September 11, 2026**, the strongest candidates are: 1. **SpaceX → Anysphere (Cursor): approximately $60 billion** — announced June 16. The all-stock transaction would make Anysphere, the company behind the AI coding assistant Cursor, a wholly owned SpaceX subsidiary. Cursor was reported to have about **$2.6 billion in annualized B2B revenue**, making this both the largest deal identified in the retrieved results and a major expansion of SpaceX into enterprise AI software and developer tools. [1] 2. **Google → Wiz: $32 billion** — announced in 2025 but **closed in March 2026**, so it is not technically a 2026 announcement. It is nevertheless one of the year's most consequential AI-adjacent acquisitions: Google's largest-ever deal brings cloud-security capabilities into Google Cloud as customers increasingly adopt AI and attackers use AI more aggressively. [2] 3. **Nvidia → Hugging Face: approximately $12.9 billion** — reported/agreed in late August and covered in September. The purchase would give Nvidia control of a major platform for distributing, testing, and collaborating on open AI models, datasets, and software. Strategically, it extends Nvidia beyond chips into the developer and open-model ecosystem, while helping it hedge against customers developing their own processors. [3][4] ### Important qualification If "announced in 2026" is interpreted strictly, **Google–Wiz should be excluded**, because the retrieved source says Google announced the intended purchase the previous year; only the closing occurred in 2026. On that strict basis, the retrieved material does not establish a comparably large third deal with enough reliable detail. The Nvidia–Hugging Face transaction is also described in some results as a report or proposed deal rather than a fully documented completed acquisition. [2][4] [1] https://www.latestly.com/technology/spacex-to-acquire-ai-coding-startup-cursor-in-usd-60-billion-deal-7475967.html [2] https://www.businessinsider.com/google-closes-wiz-acquisition-cybersecurity-2026-3 [3] https://www.pymnts.com/cpi-posts/nvidia-to-buy-hugging-face-for-12-9-billion-in-open-ai-push/ [4] https://indiantelevision.com/mam/nvidia-to-acquire-ai-startup-hugging-face-for-12-9-billion-report/ ``` ## Next steps * [Structured output](/agent/guides/structured-output): requesting a JSON result instead of Markdown * [List runs](/agent/guides/list-runs): paging this org's runs, newest first * [Poll for completion](/agent/guides/poll-for-completion): managing the wait yourself * [Cancel a run](/agent/guides/cancel-a-run): best-effort cancellation, and why it can lose the race to completion * [Handle errors](/agent/guides/handle-errors): rejected requests versus failed runs * [Reference](/agent/reference): endpoints, fields, limits and errors * [Agent API Reference](/api-reference/agent/start-run): full REST request and response specification # Cancel a run Source: https://docs.seltz.ai/agent/guides/cancel-a-run Best-effort cancellation via the Seltz Agent API, and why it can lose the race to completion To stop a run you no longer need results from, cancel it, keeping in mind that cancellation is best-effort and can lose the race to a run that is about to finish anyway. [`cancel`](/api-reference/agent/cancel-run) requests that a run stop. See [Concepts](/agent/concepts#cancellation-is-best-effort) for why a run can still complete after you cancel it. It never errors on a run that has already ended, so it is safe to call speculatively and to retry. ```python Python theme={null} cancelled = client.agent.cancel(run_id) print(cancelled.status) # may already be "completed" if the run won the race ``` ```typescript TypeScript theme={null} const cancelled = await client.agent.cancel(runId); console.log(cancelled.status); // may already be "completed" if the run won the race ``` ```bash cURL theme={null} RUN_ID="your-run-id" # the id the create response returned curl -X POST "https://api.seltz.ai/v1/agent/runs/$RUN_ID/cancel" \ -H "x-api-key: $SELTZ_API_KEY" ``` When cancellation actually takes effect, the run has no output: [`status`](/api-reference/agent/cancel-run#response-status) is `cancelled`, [`stop_reason`](/api-reference/agent/cancel-run#response-stop-reason-one-of-1) is `cancelled`, and `output`'s fields stay unset. ## Next steps * [List runs](/agent/guides/list-runs): paging this org's runs, newest first * [Poll for completion](/agent/guides/poll-for-completion): managing the wait yourself * [Reference](/agent/reference): endpoints, fields, limits and errors * [Agent API Reference](/api-reference/agent/cancel-run): full REST request and response specification # Handle errors Source: https://docs.seltz.ai/agent/guides/handle-errors Rejected requests versus failed runs in the Seltz Agent API To handle everything that can go wrong with a run, branch on whether the request itself was rejected or the run later failed, rather than assuming a successful call means a usable answer. Agent fails in two different places, and they need different handling: the same split [Fetch](/fetch/guides/handle-errors) makes between a rejected request and a failed page. A **rejected request** raises immediately. Nothing runs, and nothing costs anything: an empty [`query`](/api-reference/agent/start-run#body-query), an unknown field, or a malformed [`output_schema`](/api-reference/agent/start-run#body-output-schema-one-of-0) wrapper all fail this way. A **failed run** does not raise: [`create`](/api-reference/agent/start-run) and [`get`](/api-reference/agent/get-run) return normally, and the run reaches a terminal [`status`](/api-reference/agent/get-run#response-status): `"failed"` with [`stop_reason`](/api-reference/agent/get-run#response-stop-reason-one-of-1) saying why. ```python Python theme={null} from seltz import ( AgentRunStatus, Seltz, SeltzAPIError, SeltzAuthenticationError, SeltzConnectionError, SeltzRateLimitError, SeltzTimeoutError, ) client = Seltz() try: run = client.agent.create_and_wait( "What are the three most notable AI-company acquisitions announced in 2026?", timeout=120, ) except SeltzAuthenticationError: print("Error: Invalid API key") except SeltzConnectionError: print("Error: Could not connect to Seltz API") except SeltzTimeoutError: print("Still running -- poll get() again later, or cancel() it") except SeltzRateLimitError: print("Error: Rate limit exceeded, try again later") except SeltzAPIError as e: print(f"Request rejected: {e.grpc_code} - {e.grpc_details}") else: if run.status == AgentRunStatus.AGENT_RUN_STATUS_COMPLETED: print(run.output.text) else: print("Run did not complete:", run.stop_reason) ``` ```typescript TypeScript theme={null} import { AgentRunStatus, Seltz, SeltzAPIError, SeltzAuthenticationError, SeltzConnectionError, SeltzRateLimitError, SeltzTimeoutError, } from "seltz"; const client = new Seltz(); try { const run = await client.agent.createAndWait({ query: "What are the three most notable AI-company acquisitions announced in 2026?", timeoutMs: 120000, }); if (run.status === AgentRunStatus.COMPLETED) { console.log(run.output?.text); } else { console.log("Run did not complete:", run.stopReason); } } catch (error) { if (error instanceof SeltzAuthenticationError) { console.error("Error: Invalid API key"); } else if (error instanceof SeltzConnectionError) { console.error("Error: Could not connect to Seltz API"); } else if (error instanceof SeltzTimeoutError) { console.error("Still running -- call get() again later, or cancel() it"); } else if (error instanceof SeltzRateLimitError) { console.error("Error: Rate limit exceeded, try again later"); } else if (error instanceof SeltzAPIError) { console.error(`Request rejected: ${error.code} - ${error.message}`); } else { throw error; } } ``` ## Over REST A rejected request is a non-200 with the same `{"error": {"code": ..., "message": ...}}` envelope Search, Answer, Fetch, and Monitor use, so shared error-handling code across endpoints works for Agent, too. Branch on `code`; `message` is prose for logs and its wording can change. ```json theme={null} { "error": { "code": "INVALID_REQUEST", "message": "`query` must be non-empty" } } ``` The full status/code table is in [Reference](/agent/reference#errors). ## A failed run is not a rejection Once the server accepts a run, its eventual failure arrives as data, not as an error. Branch on `status`, then read `stop_reason` for why: ```python Python theme={null} if run.status == AgentRunStatus.AGENT_RUN_STATUS_FAILED: print("Run failed:", run.stop_reason) # AgentRunStopReason.AGENT_RUN_STOP_REASON_INVALID_OUTPUT, _BUDGET_REACHED, # _TIMEOUT, or _INTERNAL_ERROR ``` ```typescript TypeScript theme={null} if (run.status === AgentRunStatus.FAILED) { console.log("Run failed:", run.stopReason); // AgentRunStopReason.INVALID_OUTPUT, BUDGET_REACHED, TIMEOUT, or INTERNAL_ERROR } ``` `internal_error` covers every fault on Seltz's side, including retrieval outages. There is no finer split, so the only response is to retry the run. This requires a new `create`, since a failed run cannot resume. A run that produces no JSON output at all fails with `stop_reason: invalid_output` rather than returning something unparsable. ## Next steps * [Basic run](/agent/guides/basic-run): the full request/response cycle, including the cURL poll * [Structured output](/agent/guides/structured-output): requesting a JSON result instead of Markdown * [Reference](/agent/reference): endpoints, fields, limits and errors * [Agent API Reference](/api-reference/agent/start-run): full REST request and response specification # List runs Source: https://docs.seltz.ai/agent/guides/list-runs Paging this org's runs via the Seltz Agent API, newest first To see every run your organization has started or find one you lost track of the ID for, list runs instead of tracking IDs yourself. [`list`](/api-reference/agent/list-runs) pages this org's runs, newest first, unpaged beyond the cursor. Page size is [`limit`](/api-reference/agent/list-runs#parameter-limit) (1-100, default 20\); pass the previous page's `next` as [`after`](/api-reference/agent/list-runs#parameter-after) to continue. ```python Python theme={null} page = client.agent.list(limit=20) for run in page.runs: print(run.id, run.status) # Next page if page.next: next_page = client.agent.list(limit=20, after=page.next) ``` ```typescript TypeScript theme={null} const page = await client.agent.list({ limit: 20 }); for (const run of page.runs) { console.log(run.id, run.status); } // Next page if (page.next) { const nextPage = await client.agent.list({ limit: 20, after: page.next }); } ``` ```bash cURL theme={null} curl "https://api.seltz.ai/v1/agent/runs?limit=20" \ -H "x-api-key: $SELTZ_API_KEY" ``` [`next`](/api-reference/agent/list-runs#response-next-one-of-0) is unset on the last page. There is no `has_more` field on this listing. An unset `next` is the signal to stop. ## Next steps * [Poll for completion](/agent/guides/poll-for-completion): managing the wait yourself * [Cancel a run](/agent/guides/cancel-a-run): best-effort cancellation, and why it can lose the race to completion * [Reference](/agent/reference): endpoints, fields, limits and errors * [Agent API Reference](/api-reference/agent/list-runs): full REST request and response specification # Poll for completion Source: https://docs.seltz.ai/agent/guides/poll-for-completion Managing the wait yourself against the Seltz Agent API, instead of create_and_wait / createAndWait To check on a run from somewhere other than where you started it, such as a serverless function, a job queue, or a plain REST caller with no waiter, poll for its status yourself instead of relying on a blocking call. `create_and_wait` / `createAndWait` cover the common case: see [Basic run](/agent/guides/basic-run) for the full walkthrough. This guide covers doing that by hand instead. ## Create, then poll `get` [`create`](/api-reference/agent/start-run) returns immediately with the run in `pending` state. Poll [`get`](/api-reference/agent/get-run) by id until [`status`](/api-reference/agent/get-run#response-status) reaches a terminal state (`completed`, `failed`, or `cancelled`); [`stop_reason`](/api-reference/agent/get-run#response-stop-reason-one-of-1) appears only once it does. ```python Python theme={null} import time from seltz import AgentRunStatus TERMINAL = { AgentRunStatus.AGENT_RUN_STATUS_COMPLETED, AgentRunStatus.AGENT_RUN_STATUS_FAILED, AgentRunStatus.AGENT_RUN_STATUS_CANCELLED, } run = client.agent.create( "What are the three most notable AI-company acquisitions announced in 2026?" ) while run.status not in TERMINAL: time.sleep(10) run = client.agent.get(run.id) print(run.status, run.stop_reason) ``` ```typescript TypeScript theme={null} import { AgentRunStatus } from "seltz"; const TERMINAL = [AgentRunStatus.COMPLETED, AgentRunStatus.FAILED, AgentRunStatus.CANCELLED]; let run = await client.agent.create({ query: "What are the three most notable AI-company acquisitions announced in 2026?", }); while (!TERMINAL.includes(run.status)) { await new Promise((r) => setTimeout(r, 10000)); run = await client.agent.get(run.id); } console.log(run.status, run.stopReason); ``` ```bash cURL theme={null} RUN_ID="your-run-id" # the id the create response returned curl "https://api.seltz.ai/v1/agent/runs/$RUN_ID" \ -H "x-api-key: $SELTZ_API_KEY" ``` > [!NOTE] > > `run.status` prints as a raw integer in Python (`3` for `completed`) unless > you compare against the named constant or call `.Name()`. This is the same > gotcha [Monitor's run status](/monitor/reference#enum-spelling) has. Over REST and in > TypeScript, `status` is already the lowercase/named form (`"completed"`, > `AgentRunStatus.COMPLETED`). This is exactly what `wait` / `createAndWait` do for you. Reach for them first, and write your own loop only when you need to poll from somewhere the blocking call does not fit, or need a different interval than the SDK default of 10 seconds. ```python Python theme={null} run = client.agent.wait(run_id, poll_interval=5, timeout=120) ``` ```typescript TypeScript theme={null} const run = await client.agent.wait(runId, { pollIntervalMs: 5000, timeoutMs: 120000 }); ``` `timeout` / `timeoutMs` bounds the wait **client-side only**. On expiry the SDK raises `SeltzTimeoutError`, but the run keeps executing server-side. Poll `get` again later, or see [Cancel a run](/agent/guides/cancel-a-run) if you no longer want the result. ## Next steps * [List runs](/agent/guides/list-runs): paging this org's runs, newest first * [Cancel a run](/agent/guides/cancel-a-run): best-effort cancellation, and why it can lose the race to completion * [Reference](/agent/reference): endpoints, fields, limits and errors * [Agent API Reference](/api-reference/agent/get-run): full REST request and response specification # Structured output Source: https://docs.seltz.ai/agent/guides/structured-output How to request a JSON result from the Seltz Agent API instead of Markdown ## Overview To get an Agent run's answer as a JSON object matching your own schema instead of freeform Markdown, request structured output by passing a schema when you start the run. ## Provide a schema Pass [`output_schema`](/api-reference/agent/start-run#body-output-schema-one-of-0) (an OpenAI-style `response_format` object) to get [`output.structured`](/api-reference/agent/start-run#response-output-one-of-1), a JSON result shaped by your schema, in addition to the usual cited [`output.text`](/api-reference/agent/start-run#response-output-one-of-1) report. ```python Python theme={null} OUTPUT_SCHEMA = { "type": "json_schema", "json_schema": { "name": "acquisitions", "schema": { "type": "object", "properties": { "acquisitions": { "type": "array", "items": { "type": "object", "properties": { "acquirer": {"type": "string"}, "target": {"type": "string"}, }, "required": ["acquirer", "target"], "additionalProperties": False, }, }, }, "required": ["acquisitions"], "additionalProperties": False, }, "strict": True, }, } run = client.agent.create_and_wait( "What are three notable AI-company acquisitions announced in 2026?", output_schema=OUTPUT_SCHEMA, ) import json result = json.loads(run.output.structured) for deal in result["acquisitions"]: print(deal["acquirer"], "->", deal["target"]) ``` ```typescript TypeScript theme={null} const outputSchema = { type: "json_schema", json_schema: { name: "acquisitions", schema: { type: "object", properties: { acquisitions: { type: "array", items: { type: "object", properties: { acquirer: { type: "string" }, target: { type: "string" }, }, required: ["acquirer", "target"], additionalProperties: false, }, }, }, required: ["acquisitions"], additionalProperties: false, }, strict: true, }, }; const run = await client.agent.createAndWait({ query: "What are three notable AI-company acquisitions announced in 2026?", outputSchema, }); const result = JSON.parse(run.output!.structured!); for (const deal of result.acquisitions) { console.log(deal.acquirer, "->", deal.target); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/agent/runs \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What are three notable AI-company acquisitions announced in 2026?", "output_schema": { "type": "json_schema", "json_schema": { "name": "acquisitions", "strict": true, "schema": { "type": "object", "properties": { "acquisitions": { "type": "array", "items": { "type": "object", "properties": { "acquirer": {"type": "string"}, "target": {"type": "string"} }, "required": ["acquirer", "target"], "additionalProperties": false } } }, "required": ["acquisitions"], "additionalProperties": false } } } }' ``` A completion for the request above looks like this: ```json theme={null} { "acquisitions": [ { "acquirer": "SpaceX", "target": "Anysphere (Cursor)" }, { "acquirer": "Zendesk", "target": "Forethought" }, { "acquirer": "Palo Alto Networks", "target": "Console" } ] } ``` The matching [`output.grounding`](/api-reference/agent/start-run#response-output-one-of-1) looks like the following, one entry per groundable field, each citing back into [`output.sources`](/api-reference/agent/start-run#response-output-one-of-1) by `source_id`: ```json theme={null} [ { "field": "acquisitions.0.acquirer", "citations": [{ "source_id": 1, "url": "https://www.latestly.com/technology/spacex-to-acquire-ai-coding-startup-cursor-in-usd-60-billion-deal-7475967.html" }] }, { "field": "acquisitions.0.target", "citations": [{ "source_id": 1, "url": "https://www.latestly.com/technology/spacex-to-acquire-ai-coding-startup-cursor-in-usd-60-billion-deal-7475967.html" }] } ] ``` ## Schema validity Create validates `output_schema`'s wrapper shape, including `type`, and `json_schema.name` / `schema` / `strict` for type `json_schema`, before billing anything, and rejects a malformed one there. It does not validate the schema's *content*. For `strict: true`, every object in the schema needs its own `additionalProperties: false`, including nested ones like the array item object above. Create accepts a schema missing it, but the run then fails with `stop_reason: invalid_output`. ## Results A run that produces no JSON output at all fails with `stop_reason: invalid_output` rather than returning something unparsable. Ungroundable fields come back `null` in `output.structured`. This is the same "don't guess" contract [Answer](/answer/guides/structured-response) follows. ## Next steps * [Basic run](/agent/guides/basic-run): the full request/response cycle, including the cURL poll * [Handle errors](/agent/guides/handle-errors): rejected requests versus failed runs * [Reference](/agent/reference): endpoints, fields, limits and errors * [Agent API Reference](/api-reference/agent/start-run#body-output-schema-one-of-0): full `output_schema` request and response specification # Quickstart Source: https://docs.seltz.ai/agent/quickstart Getting started with the Seltz Agent API Agent runs a multi-step research loop over the web and returns a cited answer: it plans its own searches, follows up on what it finds, and keeps going until it has enough to answer, rather than the one grounding search [Answer](/answer/answer-concepts) makes. This quickstart walks through a simple workflow that starts an agent run then gets its results. ## 1. Send a query A run is asynchronous. [`create`](/api-reference/agent/start-run) returns at once with the run in `pending` state; the SDKs' `create_and_wait` / `createAndWait` starts a run and blocks until it finishes, which is the easiest way to get a result end to end. The only required field is [`query`](/api-reference/agent/start-run#body-query). See [Basic run](/agent/guides/basic-run) for the cURL form, which only starts the run and needs a separate poll to read the result. ```python Python theme={null} from seltz import Seltz client = Seltz(api_key="your-api-key") run = client.agent.create_and_wait( "What are the three most notable AI-company acquisitions announced in 2026?" ) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz({ apiKey: "your-api-key" }); const run = await client.agent.createAndWait({ query: "What are the three most notable AI-company acquisitions announced in 2026?", }); ``` > [!NOTE] > > Runs typically finish in well under a minute for a simple question, but a > harder one can take several minutes and up to around an hour. A higher > [`effort`](/agent/concepts#effort-levels) raises that ceiling. `create_and_wait` / > `createAndWait` block until then; pass `timeout` / `timeoutMs` to bound the > client-side wait. ## 2. Read the result `create_and_wait` / `createAndWait` only return once the run is done, so this part is safe: a completed run's [`output.text`](/api-reference/agent/start-run#response-output-one-of-1) is a cited Markdown report, with inline `[n]` markers pointing at [`output.sources`](/api-reference/agent/start-run#response-output-one-of-1). Checking a run that is not done yet, over cURL or your own poll loop, gets you back [`status`](/api-reference/agent/get-run#response-status): `"running"` (or `"pending"`) instead, with `output`'s fields still unset. See [Basic run](/agent/guides/basic-run) for what that looks like. ```python Python theme={null} print(run.output.text) for source in run.output.sources: print(f"[{source.id}] {source.url}") ``` ```typescript TypeScript theme={null} console.log(run.output?.text); for (const source of run.output?.sources ?? []) { console.log(`[${source.id}] ${source.url}`); } ``` ```text Example output theme={null} Three notable AI-company acquisitions announced in 2026 include: - **SpaceX → Anysphere (Cursor):** an all-stock deal valuing the AI coding-assistant company at approximately $60 billion. [1] - **Zendesk → Forethought:** the AI-agent company automating customer-service interactions. [2] - **Palo Alto Networks → Console:** an AI-agent company automating IT help-desk tasks. [3] [1] https://www.latestly.com/technology/spacex-to-acquire-ai-coding-startup-cursor-in-usd-60-billion-deal-7475967.html [2] https://techcrunch.com/2026/03/11/zendesk-acquires-agentic-customer-service-startup-forethought/ [3] https://techcrunch.com/2026/09/02/palo-alto-networks-paid-500m-for-thrive-backed-console-sources-say/ ``` ## Next steps To learn more about Seltz Agent, check out the following topics: * [Concepts](/agent/concepts): the run lifecycle, output shapes, and citations * [Basic run](/agent/guides/basic-run): the full request/response cycle, including the cURL poll * [Structured output](/agent/guides/structured-output): requesting a JSON result instead of Markdown * [List runs](/agent/guides/list-runs): paging this org's runs, newest first * [Poll for completion](/agent/guides/poll-for-completion): managing the wait yourself * [Cancel a run](/agent/guides/cancel-a-run): best-effort cancellation, and why it can lose the race to completion * [Handle errors](/agent/guides/handle-errors): rejected requests versus failed runs * [Reference](/agent/reference): endpoints, fields, limits and errors * [Agent API Reference](/api-reference/agent/start-run): full REST request and response specification # Reference Source: https://docs.seltz.ai/agent/reference Reference information for the Seltz Agent API ## Overview This topic provides detailed information about the Agent API and its access methods. ## Endpoints | Method | Path | Endpoint | | - | - | - | | `POST` | `/v1/agent/runs` | Start a run | | `GET` | `/v1/agent/runs` | List runs | | `GET` | `/v1/agent/runs/{id}` | Retrieve a run | | `POST` | `/v1/agent/runs/{id}/cancel` | Cancel a run | Over REST, authenticate with the `x-api-key` header against `https://api.seltz.ai`. The SDKs speak gRPC to `grpc.seltz.ai`; pass your API key to the client constructor, which sends it on every call. Each endpoint has an interactive request/response playground: [Start a run](/api-reference/agent/start-run), [List runs](/api-reference/agent/list-runs), [Retrieve a run](/api-reference/agent/get-run), and [Cancel a run](/api-reference/agent/cancel-run). ## SDK methods `create`, `get`, `cancel`, and the waiters all return the bare `AgentRun` envelope: the per-RPC response messages carry nothing else. `list` returns the page (`runs` plus the `next` cursor). | Python | TypeScript | Returns | | - | - | - | | `create` | `create` | The new run, in `pending` state | | `get` | `get` | The run | | `list` | `list` | `runs`, `next` | | `cancel` | `cancel` | The run's current envelope | | `wait` | `wait` | The run, once it reaches a terminal state | | `create_and_wait` | `createAndWait` | `create` then `wait` in one call | > [!NOTE] > > `wait` / `createAndWait` and manual polling via `get` are the only ways to > observe completion. There is no webhook or streaming option for Agent, unlike > [Monitor](/monitor/guides/webhooks). ## Request | Field | Type | Required | Description | | - | - | - | - | | `query` | string | Yes | The natural-language question. The model honors instructions inside it rather than tokenizing them. | | `output_schema` | object | No | OpenAI-style `response_format` object (`{"type": "text" \| "json_object" \| "json_schema", ...}`). Adds `output.structured` and its `grounding`. Create validates only the wrapper shape. | | `effort` | string | No | One of `low`, `medium`, `high`, `max`. Controls how much research depth the run applies; also affects price. Omitted defaults to `medium`. An unrecognized value is rejected with `INVALID_EFFORT`. | REST rejects an unknown field in the body; the SDKs' request types have no room for one to begin with. ## Run envelope `create`, `get`, `cancel`, and each entry `list` returns all share this one `AgentRun` shape, unchanged across REST, gRPC, and the SDKs: | Field | Set when | | - | - | | `id` | Always. | | `object` | Always `"agent.run"`. | | `status` | Always. `pending`, `running`, `completed`, `failed`, or `cancelled`. | | `stop_reason` | Once the run reaches a terminal state. Never set before. | | `created_at` | Always. | | `started_at` | Once a worker picks the run up. | | `completed_at` | Once the run reaches a terminal state. | | `request` | Always. Echo of the accepted request (`query`, `output_schema`). | | `output` | Always present as an object; its fields are unset until the run completes. | ## Output | Field | Set when | | - | - | | `text` | The run reached a terminal state with usable output. Cited Markdown; inline `[n]` markers index into `sources`. | | `structured` | The request carried an `output_schema` of type `json_object` or `json_schema`, and the run produced usable output. JSON-encoded. | | `sources` | The run consulted at least one page. Each entry has `id` (starting at 1) and `url`. | | `grounding` | `structured` is set. One entry per groundable field, keyed by dot-notation `field` path, each with `citations` pointing into `sources` by `source_id`. | ## Run status and stop reason `status` is the coarse outcome; `stop_reason` appears only once a run reaches a terminal state, and only these pairs are legal: | `status` | `stop_reason` | Meaning | | - | - | - | | `completed` | `finished` | The agent decided it was done. | | `completed` | `budget_reached` | Budget ran out, but the output so far is usable. | | `completed` | `timeout` | The wall-clock limit hit, but the output so far is usable. | | `failed` | `budget_reached` | Budget ran out with nothing usable. | | `failed` | `timeout` | The wall-clock limit hit with nothing usable. | | `failed` | `invalid_output` | The request carried an `output_schema`, and the run could produce no JSON output at all. A missing `additionalProperties: false` on a `strict: true` schema is a common cause; see [Structured output](/agent/guides/structured-output). | | `failed` | `internal_error` | A fault on Seltz's side, including retrieval outages. Retry. | | `cancelled` | `cancelled` | Stopped on request. `output` stays unset. | ## Enum spelling Each surface spells the same enum value differently. | Where | Spelling | | - | - | | REST / JSON | `"pending"`, `"running"`, `"completed"`, `"failed"`, `"cancelled"` (lowercase) | | TypeScript SDK | `AgentRunStatus.PENDING`, `.RUNNING`, `.COMPLETED`, `.FAILED`, `.CANCELLED` | | Python SDK | `AgentRunStatus.AGENT_RUN_STATUS_PENDING`, `..._RUNNING`, `..._COMPLETED`, `..._FAILED`, `..._CANCELLED` | `stop_reason` follows the same pattern: REST/JSON is lowercase (`"finished"`, `"budget_reached"`, ...), TypeScript is `AgentRunStopReason.FINISHED`, and Python is `AgentRunStopReason.AGENT_RUN_STOP_REASON_FINISHED`. > [!NOTE] > > `run.status` prints as a raw integer in Python unless compared against the > named constant. See [Poll for completion](/agent/guides/poll-for-completion). ## Pagination `list` takes `limit` (1–100, default 20) and `after`, the previous page's `next`. `next` is unset on the last page; there is no `has_more` field on this listing. ## Errors A rejected request is a non-200 with the same `{"error": {"code": ..., "message": ...}}` envelope every other Seltz endpoint uses. `code` is the stable field to branch on; `message` is prose and can change. ```json theme={null} { "error": { "code": "INVALID_REQUEST", "message": "`query` must be non-empty" } } ``` | Status | Code | When | | - | - | - | | 400 | `INVALID_REQUEST` | Malformed body, unknown field, empty `query`, or a rejected `output_schema` (create); malformed or unknown query parameter (list). | | 400 | `INVALID_EFFORT` | `effort` isn't one of `low`, `medium`, `high`, `max`. | | 401 | `UNAUTHENTICATED` | Invalid, expired, revoked, or missing API key. | | 402 | `INSUFFICIENT_CREDITS` | Insufficient budget to start the run. | | 404 | `NOT_FOUND` | No such run in this org, or (on `list`) the `after` cursor does not resolve to one. | > [!NOTE] > > Over gRPC the same failures arrive as status codes (`INVALID_ARGUMENT`, > `UNAUTHENTICATED`, `NOT_FOUND`, ...) rather than as this body. A **failed run** is not a rejection. The call itself succeeds, and the failure arrives as `status: "failed"` with `stop_reason` explaining why. See [Handle errors](/agent/guides/handle-errors). ## Timing Runs typically finish in well under a minute for a simple question, at any `effort` level. A harder one can take several minutes, and up to around an hour in the worst case. A higher `effort` raises the ceiling for a hard question; it does not slow an easy one. There is no fixed SLA on run duration; `budget_reached` and `timeout` exist precisely because a run does not run forever. # Answer concepts Source: https://docs.seltz.ai/answer/answer-concepts Understanding the Answer endpoint ## Overview The `/answer` endpoint performs single-shot retrieval-augmented generation (RAG): it runs a grounding search automatically, then returns a direct, natural-language answer with citations to the sources it used. > [!NOTE] > > **Preview.** `/answer` grounds in the [`web`](/data/web) [scope](/data/data-concepts#scopes) by default, which is in beta. Pass `scope` to ground elsewhere — for example, `news` for current-events questions such as *"Who is going to be Apple's next CEO?"*. ## Answer tiers The `model` parameter selects how much work Seltz does to answer your question: | Model | Behavior | | - | - | | `seltz-base` | Default. One grounding search, then a direct answer. | | `seltz-pro` | Agentic RAG — the model reads your request and issues the search query itself, then answers from the results. | An omitted or unrecognized `model` falls back to `seltz-base`; the response shape is unchanged either way. ## Structured output and citations By default, an answer is Markdown text with citations linked inline. Requesting a `response_format` changes the answer to a JSON string matching your schema — under structured output, inline citations no longer appear in the answer text, but the `citations` field is still returned. A malformed `response_format` is rejected before the request is billed. ## Next steps To learn more about Seltz answer, check out the following topics: * [Basic answer](/answer/guides/basic-answer) — Making your first answer request * [Request a structured response](/answer/guides/structured-response) — Requesting a specific response format * [Steer tone and style](/answer/guides/system-prompt) — Steering tone, voice, and format * [Answer API Reference](/api-reference/answer) — Full request and response specification * [Errors](/api-reference/errors) — Status codes and error handling for Search and Answer # Answer reference Source: https://docs.seltz.ai/answer/answer-reference Options, parameters, and specifications for Seltz Answer This topic provides reference information for the `/answer` endpoint. ## Request Parameters See the [Answer API Reference](/api-reference/answer) for the full list of request parameters, their types, and whether they're required. For how the `model` tiers and `response_format` behave, see [Answer concepts](/answer/answer-concepts). ## Error Responses Errors return the shared JSON envelope, with a stable `error.code` to branch on: ```json theme={null} { "error": { "code": "UNAUTHENTICATED", "message": "Invalid, expired, or revoked API key" } } ``` See [Errors](/api-reference/errors) for the full list of status codes and the code each one carries. # Basic answer Source: https://docs.seltz.ai/answer/guides/basic-answer How to answer a question using supported SDKs or REST. To answer a natural language question, call the `answer` endpoint with a question. The response will include a direct answer and a list of citations to the sources used to generate the answer. ```python Python theme={null} from seltz import Seltz client = Seltz() # Get a direct answer with citations answer = client.answer("Who is going to be Apple's next CEO?") print(answer.answer) for citation in answer.citations: print(citation.url) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); // Get a direct answer with citations const answer = await client.answer({ query: "Who is going to be Apple's next CEO?" }); console.log(answer.answer); for (const citation of answer.citations) { console.log(citation.url); } ``` ```bash cURL theme={null} # Get a direct answer with citations curl -X POST https://api.seltz.ai/v1/answer \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "Who is going to be Apple'\''s next CEO?"}' ``` # Request a structured response Source: https://docs.seltz.ai/answer/guides/structured-response How to request a structured response from the Answer endpoint To request an answer in a specific response format, pass an OpenAI-style `responseFormat` object to the `answer` method. The returned `result.answer` contains a JSON string matching your schema instead of Markdown. Inline citations no longer appear in the answer text. The `result.citations` field is returned regardless of the response format. If the `responseFormat` is malformed, it is rejected before the request is billed, and an error is raised as `SeltzAPIError`. > [!WARNING] > > Answer does not validate the model's output against your schema before returning it. A request can succeed with a `200` and still return an `answer` that is cut off partway through if, for example, generation is truncated, in which case the response does not parse as valid JSON. Always wrap your parse call in a try/catch or equivalent rather than assuming `answer` is well-formed. ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const result = await client.answer({ query: "Who is going to be Apple's next CEO?", responseFormat: { type: "json_schema", json_schema: { name: "leadership_change", schema: { type: "object", properties: { successor: { type: "string" }, effective_date: { type: "string" }, }, required: ["successor", "effective_date"], additionalProperties: false, }, strict: true, }, }, }); try { const change = JSON.parse(result.answer); console.log(change.successor, change.effective_date); } catch { console.log("Answer was not valid JSON. It may have been truncated."); } ``` ```python Python theme={null} import json from seltz import Seltz client = Seltz() response = client.answer( "Who is going to be Apple's next CEO?", response_format={ "type": "json_schema", "json_schema": { "name": "leadership_change", "schema": { "type": "object", "properties": { "successor": {"type": "string"}, "effective_date": {"type": "string"}, }, "required": ["successor", "effective_date"], "additionalProperties": False, }, "strict": True, }, }, ) try: change = json.loads(response.answer) print(change["successor"], change["effective_date"]) except json.JSONDecodeError: print("Answer was not valid JSON. It may have been truncated.") ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/answer \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Who will be the next CEO of Apple?", "response_format": { "type": "json_schema", "json_schema": { "name": "leadership_change", "strict": true, "schema": { "type": "object", "properties": { "successor": {"type": "string"}, "effective_date": {"type": "string"} }, "required": ["successor", "effective_date"], "additionalProperties": false } } } }' ``` The response's `answer` field is a JSON string matching the schema above: ```json JSON theme={null} { "answer": "{\"successor\":\"John Ternus\",\"effective_date\":\"2026-09-01\"}", "citations": [ { "url": "https://in.mashable.com/tech/100649/inside-apples-boardroom-who-will-succeed-tim-cook-as-ceo" }, { "url": "https://www.newsgram.com/science/2026/04/21/apple-tim-cook-steps-down-who-is-john-ternus" }, { "url": "https://fool.com/investing/2021/04/10/who-will-be-apples-next-ceo" }, { "url": "https://www.mactrast.com/2026/07/tim-cook-thanks-shareholders-who-have-put-there-trust-in-us-for-so-many-years/" }, { "url": "https://www.macrumors.com/2026/07/30/tim-cook-thanks-apple-shareholders/" }, { "url": "https://www.inkl.com/news/apples-next-ceo-inherits-a-product-empire-hungry-for-its-next-breakthrough" }, { "url": "https://www.macrumors.com/2026/08/03/apple-john-ternus-hiring-retired-vp/" }, { "url": "https://www.explosion.com/207823/tim-cooks-final-earnings-call-hes-all-in-on-john-ternus/" }, { "url": "https://9to5mac.com/2026/07/30/tim-cook-marks-final-earnings-call-as-apple-ceo-confirms-ternus-will-take-over-calls/" }, { "url": "https://timesofindia.indiatimes.com/technology/tech-news/tim-cook-thanks-shareholders-in-last-earnings-call-as-apple-ceo-says-we-have-a-bright-future-ahead/articleshow/132772693.cms" } ] } ``` ## Next steps * [Structured responses](/data/structured-responses): Step-by-step guidance on schema design, query anchoring, and worked examples across every data scope # Steer tone and style Source: https://docs.seltz.ai/answer/guides/system-prompt How to request a specific tone, voice, or format for the answer using the `systemPrompt` parameter To request an answer in specific tone, voice, or format, provide a system prompt to the `Answer` endpoint. If a [response format](/answer/guides/structured-response) is also specified, the system prompt is applied to the content of the structured response. ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const briefingStyle = ` Write as an analyst briefing an executive. Open with a one-line bottom line, then at most three supporting bullets. Avoid hedging language. `; const result = await client.answer({ query: "Who will be the next CEO of Apple?", systemPrompt: briefingStyle, }); console.log(result.answer); ``` ```python Python theme={null} from seltz import Seltz client = Seltz() BRIEFING_STYLE = """ Write as an analyst briefing an executive. Open with a one-line bottom line, then at most three supporting bullets. Avoid hedging language. """ response = client.answer( "Who will be the next CEO of Apple?", system_prompt=BRIEFING_STYLE, ) print(response.answer) ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/answer \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Who will be the next CEO of Apple?", "system_prompt": "Write as an analyst briefing an executive. Open with a one-line bottom line, then at most three supporting bullets. Avoid hedging language." }' ``` The following example response reflects the system prompt: ```markdown Markdown theme={null} **John Ternus will be Apple’s next CEO, effective September 1, 2026.** ([MacRumors](https://www.macrumors.com/2026/07/30/tim-cook-thanks-apple-shareholders/)) - Tim Cook said this was his final earnings call as CEO and that “John will lead these calls going forward.” ([MacRumors](https://www.macrumors.com/2026/07/30/tim-cook-thanks-apple-shareholders/)) - Cook will transition to executive chairman when Ternus takes over. ([MacRumors](https://www.macrumors.com/2026/07/30/tim-cook-thanks-apple-shareholders/)) - Other results also state that Apple has announced John Ternus as the incoming CEO and that Cook will step down in September 2026. ([NewsGram](https://www.newsgram.com/science/2026/04/21/apple-tim-cook-steps-down-who-is-john-ternus), [9to5Mac](https://9to5mac.com/2026/07/30/tim-cook-marks-final-earnings-call-as-apple-ceo-confirms-ternus-will-take-over-calls/)) ``` ```json JSON theme={null} { "answer": "**John Ternus will be Apple’s next CEO, effective September 1, 2026.** ([MacRumors](https://www.macrumors.com/2026/07/30/tim-cook-thanks-apple-shareholders/))\n\n- Tim Cook said this was his final earnings call as CEO and that “John will lead these calls going forward.” ([MacRumors](https://www.macrumors.com/2026/07/30/tim-cook-thanks-apple-shareholders/))\n- Cook will transition to executive chairman when Ternus takes over. ([MacRumors](https://www.macrumors.com/2026/07/30/tim-cook-thanks-apple-shareholders/))\n- Other results also state that Apple has announced John Ternus as the incoming CEO and that Cook will step down in September 2026. ([NewsGram](https://www.newsgram.com/science/2026/04/21/apple-tim-cook-steps-down-who-is-john-ternus), [9to5Mac](https://9to5mac.com/2026/07/30/tim-cook-marks-final-earnings-call-as-apple-ceo-confirms-ternus-will-take-over-calls/))", "citations": [ { "url": "https://in.mashable.com/tech/100649/inside-apples-boardroom-who-will-succeed-tim-cook-as-ceo" }, { "url": "https://www.newsgram.com/science/2026/04/21/apple-tim-cook-steps-down-who-is-john-ternus" }, { "url": "https://fool.com/investing/2021/04/10/who-will-be-apples-next-ceo" }, { "url": "https://www.mactrast.com/2026/07/tim-cook-thanks-shareholders-who-have-put-there-trust-in-us-for-so-many-years/" }, { "url": "https://www.macrumors.com/2026/07/30/tim-cook-thanks-apple-shareholders/" }, { "url": "https://www.inkl.com/news/apples-next-ceo-inherits-a-product-empire-hungry-for-its-next-breakthrough" }, { "url": "https://www.macrumors.com/2026/08/03/apple-john-ternus-hiring-retired-vp/" }, { "url": "https://www.explosion.com/207823/tim-cooks-final-earnings-call-hes-all-in-on-john-ternus/" }, { "url": "https://9to5mac.com/2026/07/30/tim-cook-marks-final-earnings-call-as-apple-ceo-confirms-ternus-will-take-over-calls/" }, { "url": "https://timesofindia.indiatimes.com/technology/tech-news/tim-cook-thanks-shareholders-in-last-earnings-call-as-apple-ceo-says-we-have-a-bright-future-ahead/articleshow/132772693.cms" } ] } ``` # Cancel run Source: https://docs.seltz.ai/api-reference/agent/cancel-run api-reference/openapi.json POST /v1/agent/runs/{id}/cancel Stop a run that has not finished. Returns the run, unchanged if it had already ended, so cancelling is safe to retry. # Get run Source: https://docs.seltz.ai/api-reference/agent/get-run api-reference/openapi.json GET /v1/agent/runs/{id} Poll until `status` reaches a terminal state. # List runs Source: https://docs.seltz.ai/api-reference/agent/list-runs api-reference/openapi.json GET /v1/agent/runs The organization's runs, newest first. Pass one page's `next` as the following request's `after`. # Start run Source: https://docs.seltz.ai/api-reference/agent/start-run api-reference/openapi.json POST /v1/agent/runs Returns the new run in `pending` state. Poll it by id until `status` reaches a terminal state. # Answer Source: https://docs.seltz.ai/api-reference/answer api-reference/openapi.json POST /v1/answer # Errors Source: https://docs.seltz.ai/api-reference/errors Error response shape and status codes for the Search and Answer endpoints ## Response shape Every error returns `application/json` carrying the same envelope: ```json theme={null} { "error": { "code": "INVALID_REQUEST", "message": "max_characters_per_result must be between 100 and 1000000, got 50" } } ``` Branch on `code`. It is a stable, all-caps name drawn from a closed set, and the codes each endpoint can return are listed below. `message` is a human-readable summary written for a person reading a log — it may be reworded at any time, so don't match on it. > [!NOTE] > > `/chat/completions` is the exception. It returns the OpenAI-shaped envelope (`type`, `param`, lower-case `code`) so that unmodified OpenAI SDKs work against it. See [Chat Completions reference](/chat-completions/reference#errors). > [!NOTE] > > When `stream: true` on `/answer` and a failure happens after streaming has already started, it's delivered as a single terminal SSE frame under HTTP `200`, with no trailing `data: [DONE]`, rather than as a top-level error status. That frame carries the same `code` as the buffered response: > > ``` > data: {"id":"...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"error"}],"error":{"code":"UNAUTHENTICATED","message":"Invalid, expired, or revoked API key"}} > ``` > > Errors caught before streaming starts — an invalid API key, an unrecognized `scope`, an oversized query — still return their normal status code and JSON body, whether or not `stream` is set. ## Status codes | Status | `error.code` | Applies to | Description | | - | - | - | - | | 400 | `INVALID_REQUEST` | `search`, `answer` | Malformed body, an empty or whitespace-only `query`, or a parameter outside its accepted range. | | 401 | `UNAUTHENTICATED` | `search`, `answer` | Invalid or missing API key. | | 402 | `INSUFFICIENT_CREDITS` | `search`, `answer` | Insufficient account balance. | | 404 | `NOT_FOUND` | `search`, `answer` | Endpoint not found, or a scope that matches nothing. | | 405 | `METHOD_NOT_ALLOWED` | `search`, `answer` | Wrong HTTP method for this endpoint. | | 408 | `DEADLINE_EXCEEDED` | `answer` only | The answer did not complete in time. | | 413 | `REQUEST_TOO_LARGE` | `search`, `answer` | Request body is over the size limit. | | 415 | `UNSUPPORTED_MEDIA_TYPE` | `answer` only | `Content-Type` is not `application/json`. | | 429 | `RATE_LIMITED` | `search`, `answer` | Rate limit exceeded. Wait before retrying. | | 500 | `INTERNAL` | `search`, `answer` | Internal server error. | | 502 | `INTERNAL` | `answer` only | Upstream provider failure. | | 503 | `UNAVAILABLE` | `answer` only | A backend dependency is unavailable. Safe to retry. | `413` and `429` are worth retrying only after changing the request or waiting. `503` is the one to retry unchanged. The same envelope and the same codes are used by [Fetch](/api-reference/fetch) and [Monitor](/api-reference/monitor), so one error handler covers every endpoint except `/chat/completions`. > [!NOTE] > > Codes are per endpoint, and an endpoint may define one for a condition no other has — `/agent/runs` answers a rejected `output_schema` with `INVALID_OUTPUT_SCHEMA` rather than a second flavour of `INVALID_REQUEST`. Treat an unrecognized code as a generic failure for its status class rather than rejecting the response. ## The 8 KiB limit on Answer `/answer`'s `query` and `system_prompt` fields share an 8 KiB (8,192-byte) UTF-8 limit, since both are forwarded verbatim into the upstream prompt. Exceeding it returns a `400`: ```json theme={null} { "error": { "code": "INVALID_REQUEST", "message": "query exceeds maximum length of 8192 bytes" } } ``` This limit is specific to `/answer`. An oversized `query` on `/search` does not raise an error — it simply isn't likely to match anything, and the response comes back as a normal `200` with an empty `documents` array. ## Scopes Both endpoints accept `scope`: `web` (beta), `news`, `wikipedia`, `people`, `companies`, or `financial-reports`, defaulting to `web` when omitted. An unrecognized value returns `404` with code `NOT_FOUND` and the message `Scope not found`. ## Next steps * [Search](/api-reference/search) — Full request and response specification * [Answer](/api-reference/answer) — Full request and response specification * [Data Concepts](/data/data-concepts#scopes) — What each scope contains # Fetch Source: https://docs.seltz.ai/api-reference/fetch api-reference/openapi.json POST /v1/fetch # Introduction Source: https://docs.seltz.ai/api-reference/introduction The Seltz REST API for search, answers, monitors, page fetching, and agent runs The Seltz REST API gives AI agents and applications programmatic access to the Seltz platform over HTTPS and JSON. It covers five surfaces: * **`POST /v1/search`** — context retrieval over the web. * **`POST /v1/answer`** — RAG answers grounded in search results. * **`/v1/monitors`** — saved searches, re-run on a cadence, delivering only what is new. See [Monitor](/monitor/quickstart) for the guides. * **`POST /v1/fetch`** — URLs in, LLM-ready content out. See [Fetch](/fetch/quickstart) for the guides. * **`/v1/agent/runs`** — a multi-step research loop over the web, returning a cited answer. See [Agent](/agent/quickstart) for the guides. Streaming records is server-streaming gRPC and has no REST form. Use the interactive **Endpoints** pages in this section to explore each request and response and to send live requests with the built-in playground. ## Base URL ``` https://api.seltz.ai ``` ## Authentication Every request is authenticated with an API key passed in the `x-api-key` header. Create a key in the [Seltz Console](https://console.seltz.ai/api-keys). ```bash theme={null} curl -X POST https://api.seltz.ai/v1/search \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "your search query"}' ``` ## OpenAPI specification The full machine-readable spec is available at [openapi.json](https://docs.seltz.ai/api-reference/openapi.json) — point your tooling or coding agent at it to generate clients or discover the surface. # Create monitor Source: https://docs.seltz.ai/api-reference/monitor/create-monitor api-reference/openapi.json POST /v1/monitors # Delete monitor Source: https://docs.seltz.ai/api-reference/monitor/delete-monitor api-reference/openapi.json DELETE /v1/monitors/{monitor_id} # Get monitor Source: https://docs.seltz.ai/api-reference/monitor/get-monitor api-reference/openapi.json GET /v1/monitors/{monitor_id} # Get run Source: https://docs.seltz.ai/api-reference/monitor/get-run api-reference/openapi.json GET /v1/monitors/{monitor_id}/runs/{run_id} # List monitors Source: https://docs.seltz.ai/api-reference/monitor/list-monitors api-reference/openapi.json GET /v1/monitors # List records Source: https://docs.seltz.ai/api-reference/monitor/list-records api-reference/openapi.json GET /v1/monitors/{monitor_id}/records # List run records Source: https://docs.seltz.ai/api-reference/monitor/list-run-records api-reference/openapi.json GET /v1/monitors/{monitor_id}/runs/{run_id}/records Exists so that no consumer does arithmetic on a record id: a webhook carries a run's record range as a bound, not a dense sequence. # List run requests Source: https://docs.seltz.ai/api-reference/monitor/list-run-requests api-reference/openapi.json GET /v1/monitors/{monitor_id}/runs/{run_id}/requests The only place a customer can tell *this query failed* from *there was genuinely nothing new*: records are a stream of positives, and absence cannot be inferred from presences. # List runs Source: https://docs.seltz.ai/api-reference/monitor/list-runs api-reference/openapi.json GET /v1/monitors/{monitor_id}/runs # Update monitor Source: https://docs.seltz.ai/api-reference/monitor/update-monitor api-reference/openapi.json PATCH /v1/monitors/{monitor_id} # OpenAPI Specification Source: https://docs.seltz.ai/api-reference/openapi-spec Generate clients from our OpenAPI specification. The Seltz REST API is fully documented with OpenAPI 3.1. Explore it interactively in the [REST API Reference](/api-reference/introduction), or use the specification file to generate clients and tooling in any language. The specification lives at [https://docs.seltz.ai/api-reference/openapi.json](https://docs.seltz.ai/api-reference/openapi.json). > **[Download OpenAPI Spec](/api-reference/openapi.json)** > > openapi.json ## Generate Clients Use [OpenAPI Generator](https://openapi-generator.tech/) to create clients for any language. ### Install OpenAPI Generator ```bash npm theme={null} npm install @openapitools/openapi-generator-cli -g ``` ```bash homebrew theme={null} brew install openapi-generator ``` ```bash docker theme={null} docker pull openapitools/openapi-generator-cli ``` ### Generate a Client ```bash theme={null} openapi-generator-cli generate \ -i https://docs.seltz.ai/api-reference/openapi.json \ -g \ -o ./seltz-client ``` ### Supported Languages | Language | Generator Name | | - | - | | Python | `python` | | TypeScript | `typescript-fetch` | | Go | `go` | | Java | `java` | | Ruby | `ruby` | | PHP | `php` | | C# | `csharp` | | Rust | `rust` | | Swift | `swift5` | | Kotlin | `kotlin` | See the [full list of generators](https://openapi-generator.tech/docs/generators). ## Authentication All requests are authenticated with an API key passed in the `x-api-key` header. Create a key in the [Seltz Console](https://console.seltz.ai/api-keys). See the [REST API Reference](/api-reference/introduction) for the full request and response schema of each endpoint. # Search Source: https://docs.seltz.ai/api-reference/search api-reference/openapi.json POST /v1/search # MCP Server Source: https://docs.seltz.ai/changelog/2025-12-mcp-server Introduction of the official Seltz MCP server for AI-native integrations. *Released: December 15, 2025* We're launching the official **Seltz MCP server** — enabling native integrations with Claude Desktop, Cursor, Windsurf, and other MCP-compatible AI tools. ## Highlights * **Native MCP support** — Works with any MCP-compatible client out of the box * **Direct data access** — Query your connected data sources from AI assistants * **Easy setup** — Connect directly via the hosted MCP server The MCP server is hosted at `https://mcp.seltz.ai/mcp`. > **[MCP Server](/integrations/mcp)** > > Setup instructions # Python SDK Source: https://docs.seltz.ai/changelog/2025-12-python-sdk First stable release of the official Seltz Python SDK. *Released: December 1, 2025* We're excited to announce the general availability of the **Seltz Python SDK** — a fully-typed, production-ready client for integrating Seltz into your Python applications. ## Highlights * **Idiomatic API** — Clean, Pythonic interface that feels native * **Full type hints** — Complete annotations for IDE support and static analysis * **Built-in retries** — Automatic retry logic with exponential backoff * **Async support** — Native async/await for high-performance applications Install with `pip install seltz` and start querying your data in minutes. > **[Get Started](/sdks/python/quickstart)** > > View the Python SDK documentation # Agno Integration Source: https://docs.seltz.ai/changelog/2026-01-agno-integration Build AI agents with Seltz search capabilities using the Agno framework. *Released: January 26, 2026* Seltz is now integrated with **Agno**, the lightweight framework for building AI agents. The new `SeltzTools` toolkit enables your agents to perform semantic search as part of their reasoning workflows. ## Highlights * **Native Agno toolkit** — Drop-in integration with `SeltzTools` * **Multi-model support** — Works with OpenAI, Anthropic, Groq, and more * **Research agents** — Build agents that search, synthesize, and report ## Quick Start ```python theme={null} from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.tools.seltz import SeltzTools agent = Agent( model=OpenAIChat(id="gpt-4o"), tools=[SeltzTools()], ) agent.print_response("Search for recent AI developments") ``` > **[Integration Guide](/integrations/agno)** > > Full documentation and examples > **[Agno v2.4.5 Release](https://github.com/agno-agi/agno/releases/tag/v2.4.5)** > > View the release notes # Dify Integration Source: https://docs.seltz.ai/changelog/2026-01-dify-integration Add web search capabilities to your Dify workflows with the Seltz plugin. *Released: January 28, 2026* Seltz is now available in the **Dify Marketplace**. Add real-time web search capabilities to your Dify agents and workflows. ## Highlights * **Marketplace install** — One-click installation from the Dify Marketplace * **Agent workflows** — Enable web search as an agent tool * **Workflow nodes** — Use Seltz Search as a node in your workflows ## Quick Start 1. Install Seltz from the [Dify Marketplace](https://marketplace.dify.ai/?category=all\&q=seltz) 2. Configure your API key 3. Add Seltz Search to your agents or workflows > **[Integration Guide](/integrations/dify)** > > Full documentation and examples > **[Dify Marketplace](https://marketplace.dify.ai/?category=all\&q=seltz)** > > Install from Marketplace # TypeScript SDK Source: https://docs.seltz.ai/changelog/2026-01-typescript-sdk First stable release of the official Seltz TypeScript SDK. *Released: January 21, 2026* We're excited to announce the general availability of the **Seltz TypeScript SDK** — a fully-typed, production-ready client for integrating Seltz into your Node.js applications. ## Highlights * **Full TypeScript support** — Written in TypeScript with complete type definitions * **Native gRPC** — Uses `@grpc/grpc-js` for high-performance communication * **Error handling** — Comprehensive error classes for different failure scenarios * **Environment variable support** — Automatically reads `SELTZ_API_KEY` Install with `npm install seltz` and start searching in minutes. > **[Get Started](/sdks/javascript/quickstart)** > > View the TypeScript SDK documentation # LlamaIndex Integration Source: https://docs.seltz.ai/changelog/2026-02-llamaindex-integration Add web search capabilities to your LlamaIndex agents and RAG pipelines. *Released: February 5, 2026* Seltz is now available as a **LlamaIndex tool integration**. Add context-engineered web search capabilities to your LlamaIndex agents and RAG pipelines. ## Highlights * **SeltzToolSpec** — Native LlamaIndex tool specification * **Agent integration** — Use Seltz as a tool in OpenAI, ReAct, and other agents * **RAG support** — Returns LlamaIndex `Document` objects with source metadata ## Installation ```bash theme={null} pip install llama-index-tools-seltz ``` ## Quick Start ```python theme={null} from llama_index.tools.seltz import SeltzToolSpec tool_spec = SeltzToolSpec(api_key="your-api-key") documents = tool_spec.search("Latest AI research") for doc in documents: print(doc.metadata["url"]) print(doc.text) ``` > **[Integration Guide](/integrations/llamaindex?utm_source=changelog\&utm_medium=changelog\&utm_campaign=integration)** > > Full documentation and examples > **[LlamaHub](https://llamahub.ai?utm_source=seltz\&utm_medium=docs\&utm_campaign=integration)** > > Browse on LlamaHub # Langroid Integration Source: https://docs.seltz.ai/changelog/2026-03-langroid-integration Seltz is now available as a search provider in the Langroid multi-agent framework. *Released: March 12, 2026* Seltz is now integrated into **Langroid** as a native search provider. Build multi-agent systems with context-engineered web search capabilities using `SeltzSearchTool`. ## Highlights * **SeltzSearchTool** — Stateless tool that can be enabled on any Langroid agent * **Search provider** — Available alongside Google, DuckDuckGo, and Exa in Langroid's search toolkit * **Easy setup** — Install with `pip install langroid[seltz]` and set your API key ## Installation ```bash theme={null} pip install langroid[seltz] ``` ## Quick Start ```python theme={null} import langroid as lr from langroid.agent.tools.seltz_search_tool import SeltzSearchTool agent = lr.ChatAgent(lr.ChatAgentConfig(name="Searcher")) agent.enable_message(SeltzSearchTool) task = lr.Task(agent, interactive=False) task.run("What are the latest developments in AI?") ``` > **[Integration Guide](/integrations/langroid)** > > Full documentation and examples > **[Pull Request](https://github.com/langroid/langroid/pull/992)** > > View the PR on GitHub # n8n Community Node Source: https://docs.seltz.ai/changelog/2026-04-n8n-node Seltz is now available as an n8n community node for workflow automation. *Released: April 20, 2026* Seltz is now available as an **n8n community node**. Add live web search to self-hosted n8n workflows using the `n8n-nodes-seltz` package. ## Highlights * **Community node package** - Install `n8n-nodes-seltz` from the n8n Community Nodes settings * **Web Search operation** - Send a query to Seltz and return live web documents * **Flexible outputs** - Return one item per document or all documents in a single item ## Quick Start 1. Open **Settings** > **Community Nodes** in a self-hosted n8n instance 2. Install `n8n-nodes-seltz` 3. Create a **Seltz API** credential with your API key 4. Add the Seltz node to your workflow > **[Integration Guide](/integrations/n8n?utm_source=changelog\&utm_medium=changelog\&utm_campaign=integration)** > > Full documentation and workflow setup > **[Node Package](https://ncnodes.com/package/n8n-nodes-seltz?utm_source=seltz\&utm_medium=changelog\&utm_campaign=integration)** > > View the package on NCNodes # Answer Endpoint Source: https://docs.seltz.ai/changelog/2026-05-answer-endpoint Get direct, natural-language answers grounded in live web search, with citations. *Released: May 29, 2026* Introducing the **`/answer` endpoint** — ask a question and get a direct, natural-language answer grounded in live web search results, complete with citations to the sources used. > [!NOTE] > > **Preview.** `/answer` is currently grounded in **news search**, so it's best for current-events and recent-news questions — for example, *"Who is going to be Apple's next CEO?"*. Broader web coverage is on the way. ## Highlights * **Natural-language answers** — Returns a markdown-formatted answer instead of raw documents * **Grounded in live web search** — Every answer is backed by current web sources * **Citations included** — Each answer lists the sources used, with optional full source content via `include_content` * **Available everywhere** — Python SDK `1.2.0`, TypeScript SDK `2.1.0`, and REST `POST /v1/answer` ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz() response = client.answer("Who is going to be Apple's next CEO?") print(response.answer) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const result = await client.answer({ query: "Who is going to be Apple's next CEO?" }); console.log(result.answer); ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/answer \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "Who is going to be Apple'\''s next CEO?"}' ``` > **[Get Started](/sdks/python/client)** > > View the answer endpoint documentation # OpenAI-Compatible Chat Completions Source: https://docs.seltz.ai/changelog/2026-05-chat-completions Seltz now serves an OpenAI-compatible /v1/chat/completions endpoint for web-grounded RAG. *Released: May 28, 2026* Seltz now serves an **OpenAI-compatible** `POST /v1/chat/completions` endpoint. Point an unmodified OpenAI SDK at Seltz — swap the `base_url` and API key — and every completion is grounded in **live news search**, returned with a list of source citations. > [!NOTE] > > **Preview.** Grounded in **news search** today — best for current-events questions like *"Who is going to be Apple's next CEO?"*. Broader web coverage is on the way. ## Highlights * **Drop-in OpenAI compatibility** — Set `base_url` to `https://api.seltz.ai/v1` and use your Seltz key; no code changes * **Web-grounded answers** — Every completion is grounded in live web search, with a top-level `citations[]` list * **Streaming** — `stream: true` emits `chat.completion.chunk` SSE frames terminated by `data: [DONE]` * **Structured outputs** — `response_format` supports `json_object` and strict `json_schema`, and the SDK's `.parse()` round-trips * **`include_content` extension** — Opt in to receive each cited source's full text > **[Read the Docs](/chat-completions)** > > View the Chat Completions documentation # Answer Pro Tier Source: https://docs.seltz.ai/changelog/2026-07-answer-pro Select the answer tier with the new model parameter — seltz-pro runs agentic RAG over a single grounding search. *Released: July 28, 2026* The `/answer` endpoint now takes a **`model`** parameter that selects the answer tier. It defaults to `seltz-base` (one grounding search on your query as written, then a direct answer); **`seltz-pro`** runs agentic RAG over that same single grounding search — the model reads your request and issues the search query itself, then answers from the results. ## Highlights * **New `model` parameter** — `seltz-base` (default) or `seltz-pro` * **Same response shape** — Only the answering strategy changes; citations work exactly as before * **Safe default** — An omitted or unrecognized `model` falls back to `seltz-base`, no error * **Available everywhere** — Python SDK `1.6.0`, TypeScript SDK `2.4.0`, and REST `POST /v1/answer` ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz() response = client.answer( "What did Apple announce at WWDC 2026? Provide a table.", model="seltz-pro", ) print(response.answer) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const result = await client.answer({ query: "What did Apple announce at WWDC 2026? Provide a table.", model: "seltz-pro", }); console.log(result.answer); ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/answer \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What did Apple announce at WWDC 2026? Provide a table.", "model": "seltz-pro" }' ``` > **[Get Started](/sdks/python/client)** > > View the answer endpoint documentation # Structured Output for Answer Source: https://docs.seltz.ai/changelog/2026-08-answer-structured-output Pass an OpenAI-style response_format to /answer and get the answer back as JSON matching your own schema, with citations intact. *Released: August 6, 2026* The `/answer` endpoint now takes a **`response_format`** parameter — the same OpenAI-style object `/v1/chat/completions` accepts. Under a JSON mode, the `answer` field carries a JSON string conforming to your schema instead of Markdown prose, and `citations` are returned exactly as before. ## Highlights * **New `response_format` parameter** — `{"type": "json_object"}` or `{"type": "json_schema", ...}` * **Citations survive** — Inline citations no longer appear in the answer text, but `citations[]` is still populated * **Fails fast** — A malformed `response_format` is rejected with a `400` and not billed * **Every tier** — Works on `seltz-base` and `seltz-pro`, buffered and streaming * **Available everywhere** — Python SDK `1.7.0`, TypeScript SDK `2.5.0`, and REST `POST /v1/answer` ## Quick Start ```python Python theme={null} import json from seltz import Seltz client = Seltz() response = client.answer( "Who will be the next CEO of Apple?", response_format={ "type": "json_schema", "json_schema": { "name": "leadership_change", "strict": True, "schema": { "type": "object", "properties": { "successor": {"type": "string"}, "effective_date": {"type": "string"}, }, "required": ["successor", "effective_date"], "additionalProperties": False, }, }, }, ) change = json.loads(response.answer) print(change["successor"], change["effective_date"]) # John Ternus 2026-09-01 ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const result = await client.answer({ query: "Who will be the next CEO of Apple?", responseFormat: { type: "json_schema", json_schema: { name: "leadership_change", strict: true, schema: { type: "object", properties: { successor: { type: "string" }, effective_date: { type: "string" }, }, required: ["successor", "effective_date"], additionalProperties: false, }, }, }, }); const change = JSON.parse(result.answer); console.log(change.successor, change.effective_date); // John Ternus 2026-09-01 ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/answer \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Who will be the next CEO of Apple?", "response_format": { "type": "json_schema", "json_schema": { "name": "leadership_change", "strict": true, "schema": { "type": "object", "properties": { "successor": {"type": "string"}, "effective_date": {"type": "string"} }, "required": ["successor", "effective_date"], "additionalProperties": false } } } }' ``` > **[Get Started](/sdks/python/client)** > > View the answer endpoint documentation # System Prompt for Answer Source: https://docs.seltz.ai/changelog/2026-08-answer-system-prompt Steer how an answer is written — tone, voice, format — with the new system_prompt parameter, while grounding and citations stay in force. *Released: August 6, 2026* The `/answer` endpoint now takes a **`system_prompt`** parameter: a plain string that steers how the answer is presented. It is the `/answer` equivalent of a `system` message on a chat API — subordinate to Seltz's own instructions, so the grounding contract and citations stay in force while tone, voice, and format follow yours. ## Highlights * **New `system_prompt` parameter** — a plain string, no schema or wrapper object * **Presentation only** — Retrieval is untouched: the same sources are searched and cited * **Composes** — Works alongside `response_format`, on `seltz-base` and `seltz-pro`, buffered and streaming * **Forgiving default** — Empty or whitespace-only behaves exactly as if the field were omitted * **Bounded** — Capped at 8 KiB of UTF-8; a longer value is rejected with a `400` naming the field, and not billed * **Available everywhere** — Python SDK `1.8.0`, TypeScript SDK `2.6.0`, and REST `POST /v1/answer` ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz() response = client.answer( "Who will be the next CEO of Apple?", system_prompt="Write as an analyst briefing an executive. Open with a one-line bottom line, then at most three supporting bullets. Avoid hedging language.", ) print(response.answer) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const result = await client.answer({ query: "Who will be the next CEO of Apple?", systemPrompt: "Write as an analyst briefing an executive. Open with a one-line bottom line, then at most three supporting bullets. Avoid hedging language.", }); console.log(result.answer); ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/answer \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Who will be the next CEO of Apple?", "system_prompt": "Write as an analyst briefing an executive. Open with a one-line bottom line, then at most three supporting bullets. Avoid hedging language." }' ``` ## Good to Know `system_prompt` is steering, not a guarantee — use it to shape output you asked for, not to enforce a rule. Where your instructions conflict directly with Seltz's own, Seltz's win: asking for plain text, for example, may still yield Markdown. > **[Get Started](/sdks/python/client)** > > View the answer endpoint documentation # New Data Scopes: People, Companies, and Wikipedia Source: https://docs.seltz.ai/changelog/2026-08-data-scopes Search and Answer can now scope results to dedicated people, company, and Wikipedia indexes, not just news. *Released: August 13, 2026* The [`scope`](/data/data-concepts#scopes) parameter on [`/search`](/search/guides/basic-search), [`/answer`](/answer/guides/basic-answer), and `/v1/chat/completions` now accepts three new values alongside the default `news` scope: **[`people`](/data/people)**, **[`companies`](/data/companies)**, and **[`wikipedia`](/data/wikipedia)**. Each scope searches a different, dedicated subset of the Seltz owned index, and returns results structured for that scope. ## Highlights * **`people`** — Over 800 million public professional profiles aggregated from LinkedIn, public company pages, and other social sources. Each result is a profile with fields like experience, education, skills, and certifications. * **`companies`** — Over 35 million company profiles. Each result includes fields like workforce size, financials, key executives, and recent news. * **`wikipedia`** — English-language Wikipedia articles. * **Same default as before** — Omitting `scope`, or passing an empty/whitespace-only value, still resolves to `news`. * **Available everywhere** — `/search`, `/answer`, and `/v1/chat/completions` all accept the parameter. ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz() response = client.search("acme corp", scope="companies") for document in response.documents: print(document.url) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const result = await client.search({ query: "acme corp", scope: "companies" }); for (const doc of result.documents) { console.log(doc.url); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/search \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "acme corp", "scope": "companies"}' ``` > **[Data Concepts](/data/data-concepts?utm_source=changelog\&utm_medium=changelog\&utm_campaign=scopes)** > > Understanding scopes and what each one contains > **[API Reference](/api-reference/search?utm_source=changelog\&utm_medium=changelog\&utm_campaign=scopes)** > > Full `scope` parameter specification # Introducing Agent Source: https://docs.seltz.ai/changelog/2026-09-agent Turn a question into a cited, in-depth answer. Agent plans its own searches, follows up on what it finds, and keeps going until it has enough, rather than stopping after one grounding search. *Released: September 8, 2026* **Agent** turns a question into a multi-step research loop: it plans its own searches, follows up on what it finds, and keeps going until it has enough to answer, rather than the single grounding search [Answer](/answer/answer-concepts) makes. Because that loop can take a while, a run is asynchronous: create it, then poll or wait for it to finish. ## Highlights * **Multi-step research** — Plans its own searches and follows up on what it finds, instead of grounding in a single search. * **Cited output** — `output.text` is a cited Markdown report, with inline `[n]` markers pointing at `output.sources`. * **Optional structured output** — Pass `output_schema` to also get `output.structured`, a JSON result shaped by your schema, plus `output.grounding` for field-level citations. * **Async by design** — `create` returns at once with the run in `pending` state; poll `get` yourself, or let `wait` / `createAndWait` do it for you. * **Best-effort cancellation** — `cancel` requests that a run stop; a run close to finishing can still complete before cancellation takes effect. ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz(api_key="your-api-key") run = client.agent.create_and_wait( "What are the three most notable AI-company acquisitions announced in 2026?" ) print(run.output.text) for source in run.output.sources: print(f"[{source.id}] {source.url}") ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz({ apiKey: "your-api-key" }); const run = await client.agent.createAndWait({ query: "What are the three most notable AI-company acquisitions announced in 2026?", }); console.log(run.output?.text); for (const source of run.output?.sources ?? []) { console.log(`[${source.id}] ${source.url}`); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/agent/runs \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "What are the three most notable AI-company acquisitions announced in 2026?"}' ``` > **[Concepts](/agent/concepts?utm_source=changelog\&utm_medium=changelog\&utm_campaign=agent)** > > The run lifecycle, output shapes, and citations > **[Quickstart](/agent/quickstart?utm_source=changelog\&utm_medium=changelog\&utm_campaign=agent)** > > Make your first request # Effort Levels for Agent Source: https://docs.seltz.ai/changelog/2026-09-agent-effort Control how much research depth an Agent run applies with the new effort parameter, from a fast low pass to an exhaustive max run. *Released: September 21, 2026* [`create`](/api-reference/agent/start-run) now accepts an optional **`effort`** parameter: `low`, `medium`, `high`, or `max`. It sets a ceiling on how much research depth a run applies, and affects price. Omit it and a run executes at `medium`. ## Highlights * **Four levels** — `low`, `medium`, `high`, `max`. Omitted requests default to `medium`. * **A ceiling, not a guarantee** — Effort bounds how far a run can go; a straightforward question can still finish just as fast at `max` as at `low`. * **Validated up front** — An unrecognized value is rejected with `INVALID_EFFORT` before the run starts, not partway through. ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz(api_key="your-api-key") run = client.agent.create_and_wait( "What are the three most notable AI-company acquisitions announced in 2026?", effort="high", ) print(run.output.text) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz({ apiKey: "your-api-key" }); const run = await client.agent.createAndWait({ query: "What are the three most notable AI-company acquisitions announced in 2026?", effort: "high", }); console.log(run.output?.text); ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/agent/runs \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "What are the three most notable AI-company acquisitions announced in 2026?", "effort": "high"}' ``` > **[Concepts](/agent/concepts?utm_source=changelog\&utm_medium=changelog\&utm_campaign=agent-effort)** > > Effort levels, the run lifecycle, and output shapes > **[Reference](/agent/reference?utm_source=changelog\&utm_medium=changelog\&utm_campaign=agent-effort)** > > Full request fields and error codes # Configurable content and snippet limits Source: https://docs.seltz.ai/changelog/2026-09-configure-fields fields.content and fields.snippets now accept a config object, not just true or false. *Released: September 11, 2026* [`fields.content`](/search/guides/control-content-length) and [`fields.snippets`](/search/guides/get-snippets) each now accept an object of ceilings, alongside the existing `true`/`false`. `content` is capped at 20,000 characters by default; pass `{"max_characters_per_result": 500}` to change it, or `{"max_snippets_per_result": 5}` to bound `snippets`. ## Highlights * **Content ceiling, configurable** — `max_characters_per_result`, 100 to 1,000,000 characters. * **Snippet ceilings, configurable** — `max_snippets`, `max_snippets_per_result`, `max_tokens`, and `max_tokens_per_result` all take explicit values now, not just their built-in bounds. * **Same shape either way** — Each `fields` member still accepts a plain boolean; only callers who want a non-default ceiling need the object form. * **Rejected outside range** — An out-of-range value returns a `400` with the `INVALID_REQUEST` code before anything is billed. ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz() response = client.search( "climate change", fields={"content": {"max_characters_per_result": 500}}, ) for document in response.documents: print(document.url) print(document.content) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const result = await client.search({ query: "climate change", fields: { content: { maxCharactersPerResult: 500 } }, }); for (const doc of result.documents) { console.log(doc.url); console.log(doc.content); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/search \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "climate change", "fields": {"content": {"max_characters_per_result": 500}}}' ``` > **[Control content length](/search/guides/control-content-length?utm_source=changelog\&utm_medium=changelog\&utm_campaign=configure-fields)** > > Setting a ceiling on content per result > **[Get snippets](/search/guides/get-snippets?utm_source=changelog\&utm_medium=changelog\&utm_campaign=configure-fields)** > > Tuning how many snippets you get # Introducing Fetch Source: https://docs.seltz.ai/changelog/2026-09-fetch Turn up to 20 URLs into LLM-ready Markdown in one call. Fetch returns one result per URL, keyed by the URL you sent. *Released: September 4, 2026* **Fetch** turns a URL into LLM-ready Markdown. Send up to 20 URLs to [`POST /v1/fetch`](/api-reference/fetch) in one call and get back one result per URL: the extracted content when the page could be fetched, an error code when it could not, and what was observed on the way — where redirects landed, what status code the origin returned. ## Highlights * **Up to 20 URLs per call** — URLs are fetched concurrently, so a batch takes about as long as its slowest page. * **Markdown by default** — `formats` resolves to `["markdown"]` when omitted, returning the page's main content with navigation, footers and ads removed. * **Keyed by the URL you sent** — Results arrive in the requested order, and `requested_url` is echoed back byte for byte as the correlation key. Two identical URLs in one request are rejected, because a repeated key is ambiguous. * **A failed page is not a failed call** — A page that could not be fetched arrives inside the `200` with `status = "error"` and an `error.code`. Only the request is rejected with a non-200, and then nothing is fetched and nothing is billed. * **Per-URL time budget** — `timeout_ms` applies to one URL, not to the batch. 75 seconds is the maximum and the default; higher values are clamped, not rejected. * **One tier** — `tier` selects the price. `"pro"` is the only value and an omitted `tier` resolves to it. ## Quick Start ```python Python theme={null} from seltz import FetchStatus, Seltz client = Seltz(api_key="your-api-key") response = client.fetch(["https://example.com/"]) for result in response.results: if result.status == FetchStatus.FETCH_STATUS_OK: print(result.markdown) ``` ```typescript TypeScript theme={null} import { FetchStatus, Seltz } from "seltz"; const client = new Seltz({ apiKey: "your-api-key" }); const response = await client.fetch({ urls: ["https://example.com/"] }); for (const result of response.results) { if (result.status === FetchStatus.OK) { console.log(result.markdown); } } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/fetch \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"urls": ["https://example.com/"]}' ``` ## Good to Know Branch on `status`, not on whether `markdown` is set: a format the page could not produce is unset on an otherwise successful result. Treat an unrecognized `error.code` as a generic failure — the set grows. > **[Concepts](/fetch/concepts?utm_source=changelog\&utm_medium=changelog\&utm_campaign=fetch)** > > Requests, results and formats > **[Quickstart](/fetch/quickstart?utm_source=changelog\&utm_medium=changelog\&utm_campaign=fetch)** > > Fetch your first page # New Data Scope: Financial Reports Source: https://docs.seltz.ai/changelog/2026-09-financial-reports Search and Answer can now scope results to financial reports — 18.7 million SEC filings dating back to 1994, chunked and indexed for precise, retrieval-ready results. *Released: September 8, 2026* The [`scope`](/data/data-concepts#scopes) parameter on [`/search`](/search/guides/basic-search) and [`/answer`](/answer/guides/basic-answer) now accepts a new **financial reports** scope alongside [`news`](/data/news), [`people`](/data/people), [`companies`](/data/companies), and [`wikipedia`](/data/wikipedia). It searches over 18.7 million SEC filings dating back to 1994, chunked and indexed into more than 350 million searchable records for precise, retrieval-ready results. ## Highlights * **18.7 million filings, back to 1994** — Annual reports, quarterly filings, and more, chunked and indexed for precise retrieval. * **Works with Search and Answer** — Retrieve readable full-text chunks directly, or get an LLM-generated answer grounded in citations that point straight back to the filing. * **Narrow by publish date** — Scope results to a specific date range. * **Updated daily** — New filings are indexed daily. ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz(api_key="your-api-key") response = client.answer( "What risk factors did Acme Corp disclose in its most recent 10-K?", scope="financial-reports", ) print(response.answer) for citation in response.citations: print(citation.url) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz({ apiKey: "your-api-key" }); const response = await client.answer({ query: "What risk factors did Acme Corp disclose in its most recent 10-K?", scope: "financial-reports", }); console.log(response.answer); for (const citation of response.citations) { console.log(citation.url); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/answer \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "What risk factors did Acme Corp disclose in its most recent 10-K?", "scope": "financial-reports"}' ``` > **[Financial reports](/data/financial-reports?utm_source=changelog\&utm_medium=changelog\&utm_campaign=financial-reports)** > > Understanding the financial reports scope > **[Data Concepts](/data/data-concepts?utm_source=changelog\&utm_medium=changelog\&utm_campaign=financial-reports)** > > Understanding scopes and what each one contains # Introducing Monitor Source: https://docs.seltz.ai/changelog/2026-09-monitor Turn a set of searches into a feed. Monitor re-runs your searches on a cadence and delivers only what's new, with an optional webhook so you don't have to poll. *Released: September 2, 2026* **Monitor** turns a set of searches into a feed. Create a monitor with a list of search requests and a cadence, and it re-runs those searches on schedule, remembers every document it has already handed you, and delivers only what's new — no re-fetching the same results on every run. ## Highlights * **One monitor, many searches** — A single monitor holds a list of search requests, each with its own filters, all running on the same cadence. * **Delivers only new results** — A document is returned once and never again while the run that first delivered it is still visible. * **Webhooks instead of polling** — Attach a webhook and get notified with `run.completed` or `run.failed` when a run finishes. * **Full run history** — Inspect past runs, per-request outcomes, and every record a run produced. * **Pause without losing state** — A paused monitor keeps its configuration and records; it just stops running. ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz() response = client.monitor.create( "my-changelog-monitor", cadence="1h", search_requests=["best ai search engines", "ai search funding"], ) monitor = response.monitor print(monitor.monitor_id) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const response = await client.monitor.create({ name: "my-changelog-monitor", cadence: "1h", searchRequests: ["best ai search engines", "ai search funding"], }); const monitor = response.monitor; console.log(monitor!.monitorId); ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/monitors \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "my-changelog-monitor", "cadence": "1h", "search_requests": [ {"query": "best ai search engines"}, {"query": "ai search funding"} ] }' ``` > [!NOTE] > > Monitor is the product and SDK name throughout — `client.monitor.create()`, `SeltzMonitor`, etc. Only the REST resource path is plural, `/v1/monitors`, following standard REST collection-naming convention. > **[Concepts](/monitor/concepts?utm_source=changelog\&utm_medium=changelog\&utm_campaign=monitor)** > > Monitors, runs, records, and webhooks > **[Quickstart](/monitor/quickstart?utm_source=changelog\&utm_medium=changelog\&utm_campaign=monitor)** > > Create your first monitor # Snippets for Search Source: https://docs.seltz.ai/changelog/2026-09-search-snippets Request a document's highest-scoring passages instead of, or alongside, its full content. *Released: September 9, 2026* [`/search`](/search/guides/basic-search) can now return **snippets** instead of, or alongside, its full `content`. Snippets are a document's highest-scoring passages, in the order they appear in the document. Set `fields.snippets` to `true` to get focused excerpts without passing an entire document into your prompt or pipeline. ## Highlights * **Focused excerpts** — Each document's `snippets` array holds its highest-scoring passages, in the order they appear in the document. * **Independent of `content`** — `fields` selects each member on its own, so request `snippets`, `content`, or both in the same call. * **Available on `news` and `wikipedia`** — The `companies` and `people` scopes don't produce snippets; `snippets` comes back as an empty array for those scopes even when requested. ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz() response = client.search( "best ai search engines", fields={"snippets": True}, ) for document in response.documents: print(document.url) for snippet in document.snippets: print(f" {snippet.text}") ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const result = await client.search({ query: "best ai search engines", fields: { snippets: true }, }); for (const doc of result.documents) { console.log(doc.url); for (const snippet of doc.snippets) { console.log(` ${snippet.text}`); } } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/search \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "best ai search engines", "fields": {"snippets": true}}' ``` > **[Get Snippets](/search/guides/get-snippets?utm_source=changelog\&utm_medium=changelog\&utm_campaign=snippets)** > > Requesting snippets alongside or instead of content > **[API Reference](/api-reference/search?utm_source=changelog\&utm_medium=changelog\&utm_campaign=snippets)** > > Full `fields` parameter specification # Search Tiers: Base and Pro Source: https://docs.seltz.ai/changelog/2026-09-search-tiers Trade result quality for lower latency with the new tier parameter — pro is the quality-oriented default, base is the latency-oriented option. *Released: September 8, 2026* [`/search`](/search/search-concepts) now takes a **`tier`** parameter that picks between latency and result quality. **`pro`** — the default — is quality-oriented: the most relevant results are ordered to the top. **`base`** is latency-oriented: responses come back faster, with less precise ordering near the top. ## Highlights * **New `tier` parameter** — Choose `pro` (default, optimized for quality) or `base` (optimized for latency). * **Independent of `scope`** — `tier` picks the latency/quality trade-off, [`scope`](/data/data-concepts#scopes) picks which data is searched; every scope accepts either tier. * **Opt-out, not opt-in** — Omitting `tier` gives you `pro`, so existing calls keep the ordering they already had. Send `base` explicitly when latency matters more. * **Strict on names** — Matched case-insensitively with whitespace trimmed, but an unrecognized tier is rejected rather than quietly defaulted, so you are only ever served the tier you named. * **Available everywhere** — This setting is supported in Python SDK `1.13.0`, TypeScript SDK `2.10.0`, and REST `POST /v1/search`. ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz(api_key="your-api-key") response = client.search("Who is going to be Apple's next CEO?", tier="base") for document in response.documents: print(document.url) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz({ apiKey: "your-api-key" }); const result = await client.search({ query: "Who is going to be Apple's next CEO?", tier: "base", }); for (const doc of result.documents) { console.log(doc.url); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/search \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Who is going to be Apple'\''s next CEO?", "tier": "base" }' ``` > **[Choose a search tier](/search/guides/choose-a-tier?utm_source=changelog\&utm_medium=changelog\&utm_campaign=search-tiers)** > > When to reach for `base` over the default `pro` > **[API Reference](/api-reference/search?utm_source=changelog\&utm_medium=changelog\&utm_campaign=search-tiers)** > > Full `tier` parameter specification # New Data Scope: Web (Beta) Source: https://docs.seltz.ai/changelog/2026-10-web-scope Search, Answer, and Monitor can now search the general web. The new `web` scope is in beta and is the new default scope. *Released: October 14, 2026* The [`scope`](/data/data-concepts#scopes) parameter on [`/search`](/search/guides/basic-search) and [`/answer`](/answer/guides/basic-answer) now accepts a new **web** scope, in beta, alongside [`news`](/data/news), [`people`](/data/people), [`companies`](/data/companies), [`financial-reports`](/data/financial-reports), and [`wikipedia`](/data/wikipedia). It searches a general crawl of the web together with Wikipedia. ## Highlights * **General web coverage** — Company and product sites, documentation, blogs, reference pages, news sites, and Wikipedia in one scope. * **The new default** — A request without a `scope` now searches `web` instead of `news`. To keep the previous behavior, pass `scope: "news"`. * **Works with Search, Answer, and Monitor** — Retrieve full-text results directly, or get an answer grounded in citations from the web. * **Beta** — Coverage and ranking can change while the scope is in beta. ## Quick Start ```python Python theme={null} from seltz import Seltz client = Seltz(api_key="your-api-key") response = client.search( "How do I rotate an API key in Kubernetes secrets?", scope="web", ) for document in response.documents: print(document.url) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz({ apiKey: "your-api-key" }); const response = await client.search({ query: "How do I rotate an API key in Kubernetes secrets?", scope: "web", }); for (const document of response.documents) { console.log(document.url); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/search \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "How do I rotate an API key in Kubernetes secrets?", "scope": "web"}' ``` > **[Web](/data/web?utm_source=changelog\&utm_medium=changelog\&utm_campaign=web)** > > Understanding the web scope > **[Data Concepts](/data/data-concepts?utm_source=changelog\&utm_medium=changelog\&utm_campaign=web)** > > Understanding scopes and what each one contains # Concepts Source: https://docs.seltz.ai/chat-completions/concepts Understanding chat completions ## Overview Seltz exposes an **OpenAI-compatible** `POST /v1/chat/completions` endpoint. It's a drop-in replacement providing single-shot, web-grounded retrieval-augmented generation (RAG). > [!NOTE] > > This endpoint grounds its answers in the [`web`](/data/web) scope by default, which is in beta. To ground answers in a different scope, such as `news` for current-events questions, set the [`scope`](/chat-completions/reference) parameter. Point an OpenAI SDK at Seltz by swapping the `base_url` and API key. * **Base URL**: `https://api.seltz.ai/v1` To use the endpoint directly, make a `POST` request to `/v1/chat/completions` with the same request body as OpenAI's Chat Completions API. * **Endpoint:** `POST https://api.seltz.ai/v1/chat/completions` > [!NOTE] > > This endpoint performs **single-shot RAG**. When `messages` contains a multi-turn history, retrieval is driven by the **last user message** only: earlier turns do not influence what is retrieved. There is no conversational memory across calls. ## Pricing Each call bills as one request at the standard flat rate, the same as `/search` and `/answer`. See [Pricing](/pricing) for details. ## Next steps To learn more about Seltz chat completions, check out the following topics: * [Quickstart](/chat-completions/quickstart) — Making your first request * [Authentication](/chat-completions/guides/authentication) — How to authenticate to the chat completions endpoint * [Structured outputs](/chat-completions/guides/structured-outputs) — Requesting structured outputs * [Streaming](/chat-completions/guides/streaming) — Requesting streaming responses * [Citations](/chat-completions/guides/citations) — Requesting citations in responses * [Steer tone and format](/chat-completions/guides/steer-tone-and-format) — How to steer style and behavior of chat completions responses * [Reference](/chat-completions/reference) — Reference information for chat completions # Authentication Source: https://docs.seltz.ai/chat-completions/guides/authentication How to authenticate to the chat completions endpoint When making requests to the chat completions endpoint, use the standard OpenAI-style `Authorization: Bearer ` header. The OpenAI SDK sends this automatically once you set its `api_key`. For raw curl, pass it yourself: ```bash theme={null} -H "Authorization: Bearer $SELTZ_API_KEY" ``` # Citations Source: https://docs.seltz.ai/chat-completions/guides/citations Requesting citations in chat completions `citations` is a **top-level Seltz extension** (not part of the standard OpenAI response). It is present whenever the underlying search returned URL-bearing sources, regardless of your prompt or `response_format`. Each citation always carries a `url`. To also receive the source's text, set `include_content: true` — then each citation includes a `content` field. Otherwise `content` is omitted. `include_content` is a Seltz extension (default `false`): ```python Python theme={null} import os from openai import OpenAI client = OpenAI( base_url="https://api.seltz.ai/v1", api_key=os.environ["SELTZ_API_KEY"], ) completion = client.chat.completions.create( model="seltz-base", messages=[{"role": "user", "content": "What's the latest on Apple's CEO succession?"}], extra_body={"include_content": True}, ) for citation in completion.citations: print(citation["url"]) print(citation["content"]) ``` ```typescript TypeScript theme={null} import OpenAI from "openai"; type SeltzCompletion = OpenAI.Chat.ChatCompletion & { citations?: { url: string; content?: string }[]; }; const client = new OpenAI({ baseURL: "https://api.seltz.ai/v1", apiKey: process.env.SELTZ_API_KEY, }); const completion: SeltzCompletion = await client.chat.completions.create({ model: "seltz-base", messages: [ { role: "user", content: "What's the latest on Apple's CEO succession?" }, ], include_content: true, }); for (const citation of completion.citations ?? []) { console.log(citation.url); console.log(citation.content); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/chat/completions \ -H "Authorization: Bearer $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "seltz-base", "messages": [ {"role": "user", "content": "What'\''s the latest on Apple'\''s CEO succession?"} ], "include_content": true }' ``` # Steer tone and format of chat completions Source: https://docs.seltz.ai/chat-completions/guides/steer-tone-and-format How to steer style and behavior of chat completions responses You may include a `system` message to steer tone and format. It is honored, but **subordinate**: Seltz's own grounding and citation directives always take precedence. There may be at most one `system` message and it must come first. It is capped at 8 KiB (`MAX_SYSTEM_PROMPT_BYTES`); exceeding the cap is a 400. # Streaming chat completions Source: https://docs.seltz.ai/chat-completions/guides/streaming How to request streaming chat completions responses Set `stream: true` to receive Server-Sent Events as `chat.completion.chunk` frames: * The **first** frame carries `delta.role: "assistant"` and the top-level `citations[]` extension. * **Subsequent** frames carry `delta.content` text deltas. * The **terminal** frame carries `finish_reason`. * The stream ends with `data: [DONE]`. ```python Python theme={null} import os from openai import OpenAI client = OpenAI( base_url="https://api.seltz.ai/v1", api_key=os.environ["SELTZ_API_KEY"], ) stream = client.chat.completions.create( model="seltz-base", messages=[{"role": "user", "content": "Who is going to be Apple's next CEO?"}], stream=True, ) for chunk in stream: delta = chunk.choices[0].delta if delta.content: print(delta.content, end="") ``` ```typescript TypeScript theme={null} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.seltz.ai/v1", apiKey: process.env.SELTZ_API_KEY, }); const stream = await client.chat.completions.create({ model: "seltz-base", messages: [ { role: "user", content: "Who is going to be Apple's next CEO?" }, ], stream: true, }); for await (const chunk of stream) { const delta = chunk.choices[0].delta; if (delta.content) { process.stdout.write(delta.content); } } ``` ```bash cURL theme={null} curl -N -X POST https://api.seltz.ai/v1/chat/completions \ -H "Authorization: Bearer $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "seltz-base", "messages": [ {"role": "user", "content": "Who is going to be Apple'\''s next CEO?"} ], "stream": true }' ``` > [!WARNING] > > Once `stream: true` is set, **every** error — including authentication and validation failures — is emitted as a single terminal frame, `data: {"error":{...}}`, under **HTTP 200**, with **no** trailing `data: [DONE]`. Do not branch on the HTTP status for streaming requests. > > The error frame carries no `choices`, so a hand-rolled parser doing `chunk["choices"][0]` gets a `KeyError`. A genuine mid-stream error is always `api_error`, and any text already emitted stays on the reader's screen with no truncation marker. # Requesting structured chat completions outputs Source: https://docs.seltz.ai/chat-completions/guides/structured-outputs How to request structured outputs from the chat completions endpoint To request structured outputs, pass a `response_format` in your request. `response_format` is a passthrough field with three modes: * **Default** — plain text (Markdown) with inline citations. * `{"type": "json_object"}` — JSON object output. * `{"type": "json_schema", ...}` — strict output conforming to a named [OpenAI-compatible JSON schema](https://developers.openai.com/api/docs/guides/structured-outputs). Under either JSON mode, inline Markdown citations are suppressed, but the top-level `citations[]` extension is still present in the response. The OpenAI SDK's `.parse()` helper compiles a Pydantic model to a strict `json_schema`, sends it as the `response_format`, and parses the response back into your model, including nested models and lists: ```python Python theme={null} import os from openai import OpenAI from pydantic import BaseModel client = OpenAI(base_url="https://api.seltz.ai/v1", api_key=os.environ["SELTZ_API_KEY"]) class Candidate(BaseModel): name: str rationale: str class SuccessionOutlook(BaseModel): company: str leading_candidate: str candidates: list[Candidate] completion = client.chat.completions.parse( model="seltz-base", messages=[ {"role": "user", "content": "Who is going to be Apple's next CEO?"}, ], response_format=SuccessionOutlook, ) outlook = completion.choices[0].message.parsed print(outlook.leading_candidate) for candidate in outlook.candidates: print(f"{candidate.name}: {candidate.rationale}") # Citations are still returned as the top-level extension for citation in completion.citations: print(citation["url"]) ``` ```typescript TypeScript theme={null} import OpenAI from "openai"; import { zodResponseFormat } from "openai/helpers/zod"; import { z } from "zod"; type SeltzCompletion = OpenAI.Chat.ChatCompletion & { citations?: { url: string }[]; }; const client = new OpenAI({ baseURL: "https://api.seltz.ai/v1", apiKey: process.env.SELTZ_API_KEY, }); const Candidate = z.object({ name: z.string(), rationale: z.string(), }); const SuccessionOutlook = z.object({ company: z.string(), leading_candidate: z.string(), candidates: z.array(Candidate), }); const completion = await client.chat.completions.parse({ model: "seltz-base", messages: [ { role: "user", content: "Who is going to be Apple's next CEO?" }, ], response_format: zodResponseFormat(SuccessionOutlook, "succession_outlook"), }); const outlook = completion.choices[0].message.parsed; if (outlook === null) { throw new Error("The model refused to produce the requested structure."); } console.log(outlook.leading_candidate); for (const candidate of outlook.candidates) { console.log(`${candidate.name}: ${candidate.rationale}`); } // Citations are still returned as the top-level extension for (const citation of (completion as SeltzCompletion).citations ?? []) { console.log(citation.url); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/chat/completions \ -H "Authorization: Bearer $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "seltz-base", "messages": [ {"role": "user", "content": "Who is going to be Apple'\''s next CEO?"} ], "response_format": { "type": "json_schema", "json_schema": { "name": "succession_outlook", "strict": true, "schema": { "type": "object", "properties": { "company": {"type": "string"}, "leading_candidate": {"type": "string"}, "candidates": { "type": "array", "items": { "type": "object", "properties": { "name": {"type": "string"}, "rationale": {"type": "string"} }, "required": ["name", "rationale"], "additionalProperties": false } } }, "required": ["company", "leading_candidate", "candidates"], "additionalProperties": false } } } }' ``` ## Next steps * [Structured responses](/data/structured-responses): Step-by-step guidance on schema design, query anchoring, and worked examples across every data scope # Quickstart Source: https://docs.seltz.ai/chat-completions/quickstart Getting started with Seltz chat completions To get started using Seltz chat completion, try running the example below. The example makes a buffered call, prints the answer, and iterates over its citations: ```python Python theme={null} import os from openai import OpenAI client = OpenAI( base_url="https://api.seltz.ai/v1", api_key=os.environ["SELTZ_API_KEY"], ) completion = client.chat.completions.create( model="seltz-base", messages=[ {"role": "user", "content": "Who is going to be Apple's next CEO?"}, ], ) print(completion.choices[0].message.content) for citation in completion.citations: print(citation["url"]) ``` ```typescript TypeScript theme={null} import OpenAI from "openai"; // citations is a Seltz extension, so it is not on the OpenAI response type. // It is present when the search returned URL-bearing sources. type SeltzCompletion = OpenAI.Chat.ChatCompletion & { citations?: { url: string }[]; }; const client = new OpenAI({ baseURL: "https://api.seltz.ai/v1", apiKey: process.env.SELTZ_API_KEY, }); const completion: SeltzCompletion = await client.chat.completions.create({ model: "seltz-base", messages: [ { role: "user", content: "Who is going to be Apple's next CEO?" }, ], }); console.log(completion.choices[0].message.content); for (const citation of completion.citations ?? []) { console.log(citation.url); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/chat/completions \ -H "Authorization: Bearer $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "seltz-base", "messages": [ {"role": "user", "content": "Who is going to be Apple'\''s next CEO?"} ] }' ``` ## Next steps To learn more about Seltz chat completions, check out the following topics: * [Concepts](/chat-completions/concepts) — Understanding chat completions * [Authentication](/chat-completions/guides/authentication) — How to authenticate to the chat completions endpoint * [Structured outputs](/chat-completions/guides/structured-outputs) — Requesting structured outputs * [Streaming](/chat-completions/guides/streaming) — Requesting streaming responses * [Citations](/chat-completions/guides/citations) — Requesting citations in responses * [Steer tone and format](/chat-completions/guides/steer-tone-and-format) — How to steer style and behavior of chat completions responses * [Reference](/chat-completions/reference) — Reference information for chat completions # Reference Source: https://docs.seltz.ai/chat-completions/reference Reference information for using the chat completions endpoint ## Request Fields > [!NOTE] > > The `model` field selects the answer tier. It is optional — an absent or unrecognized value falls back to `seltz-base`. | Field | Type | Required | Description | | - | - | - | - | | `messages` | array | Yes | Array of `{ role, content }`. `role` is one of `system`, `user`, or `assistant`. `content` is a string or an array of content parts — text parts are concatenated in order; image/audio parts are silently dropped (text-only grounding). At least one `user` message is required. | | `model` | string | No | Selects the answer tier: `seltz-base` (default) or `seltz-pro`. An absent or unrecognized value falls back to `seltz-base`. | | `stream` | boolean | No | Stream `chat.completion.chunk` SSE events terminated by `data: [DONE]`. Default: `false` (one buffered `chat.completion` object). | | `response_format` | object | No | Passthrough. `{"type":"json_object"}` or `{"type":"json_schema", ...}` enable JSON output. Default: text. | | `include_content` | boolean | No | **Seltz extension.** Include each cited source's `content` text in `citations[]`. Default: `false`. | | `scope` | string | No | **Seltz extension.** Route the grounding search to a specific vertical: `web` (beta), `news`, `wikipedia`, `people`, `companies`, or `financial-reports`. Default: `web`. | ## Response A buffered response is a `chat.completion` object with exactly one `choices` entry (`index: 0`). ```json theme={null} { "id": "chatcmpl-", "object": "chat.completion", "created": 1730000000, "model": "seltz-base", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "" }, "finish_reason": "stop" } ], "citations": [ { "url": "https://example.com/article", "content": "Source text (only when include_content=true)" } ] } ``` * `id` has the form `chatcmpl-`. * `citations` is a top-level Seltz extension. It is always present, and is `[]` when search returned no URL-bearing sources. Each entry always has `url`; `content` appears only when `include_content` is `true`, and is omitted when the source has no fetched content. > [!NOTE] > > There is no `usage` field on the response. ## Errors Errors use the OpenAI-shaped envelope. The status codes below apply to non-streaming requests only: when `stream: true`, every error is returned as an SSE error frame under HTTP 200. See [Streaming](/chat-completions/guides/streaming). ```json theme={null} { "error": { "message": "...", "type": "invalid_request_error", "param": "messages", "code": "unsupported_role" } } ``` | HTTP | Type | When | | - | - | - | | 400 | `invalid_request_error` | No user message; empty or whitespace query; query larger than 8 KiB (`MAX_QUERY_BYTES`, measured on trimmed content); malformed `response_format` (rejected pre-auth — no billing, no search); empty `messages`; unparseable body; unsupported role; more than one `system` message, or a `system` message not at index 0; `system` message larger than 8 KiB. | | 401 | `authentication_error` | Invalid or missing API key. | | 402 | `insufficient_quota` | Account quota exhausted. | | 403 | `permission_error` | Key not permitted for this endpoint. | | 404 | `not_found_error` | Resource not found. | | 408 | `timeout` | Upstream timed out. | | 413 | — | Request body exceeds the per-route cap. Rejected before parsing, so the body is plain text, **not** the JSON envelope. | | 429 | `rate_limit_error` | Too many requests. Retry with backoff. | | 500 | `api_error` | Internal error. | | 502 | `api_error` | Upstream unavailable. Retry with backoff. | # Companies Source: https://docs.seltz.ai/data/companies Understanding the companies scope The `companies` scope contains over 35 million company profiles from social media and company sites. Each result in the response is a different company matching your search. ## Includes This scope includes information contained in the public profiles of companies, including their name, industry, location, and company details. ## Excludes This scope excludes private information, such as personal contact information, private social media profiles, and any information not publicly available. ## Structure Search results from the `company` scope are structured as a list of profiles, with each profile containing the following fields: * Company name * Industry * Ownership type * HQ location * Company details * About -- company-authored boilerplate * Categories * Talent -- talent sources and alumni destinations, top 10 each * Workforce -- exact employee count, headcount growth MoM/YoY * Headcount by Country * Key Executives -- name and title * Company Updates -- dated LinkedIn posts, raw * Products & Technology -- tech stack * Web Presence -- monthly visits, MoM change, global rank, bounce rate, pages/visit, top countries with percentages * Reviews & Ratings -- employer rating out of 5, review count * Competitors * Recent News -- dated, with publication name * Financials -- total raised, then round type/date/amount * Acquisitions -- target and date * Hiring -- active posting count plus sample titles ## Sample response Search results with the `companies` scope in JSON format have the following structure: ```json theme={null} { "documents": [ { "url": "https://www.linkedin.com/company/seltz", "content": "# Seltz\n\n**Software Development · Privately Held · San Francisco, United States**\n\n## About\n\nThe Web Knowledge Layer for AI Agents.\n\n## Company Details\n\n* Legal name: Seltz, Inc.\n* Also known as: seltz, seltz, inc.\n* Industry: Software Development\n* Type: Privately Held\n* Status: active\n* Business model: B2B\n* Size: 1-10 employees\n* Headquarters: 71 Stevenson St, Suite 400, San Francisco, CA 94195, US\n* Region: Americas, Northern America, AMER\n* Website: https://www.seltz.ai/\n* LinkedIn: https://www.linkedin.com/company/seltz\n* LinkedIn followers: 1373\n* Crunchbase: https://www.crunchbase.com/organization/seltz-1811\n* Email: hello@seltz.ai\n\n## Financials\n\nTotal funding raised: $12500000\n\n- Seed Round · \n- Seed Round · 2026-06-01 · 12500000 USD\n\n## Workforce\n\n* Employees: 18\n\n## Headcount by Country\n\nGermany: 3 • United States: 9 • Italy: 3\n\n## Talent\n\n* Talent sources: Salesforce, Università di Pisa, Friedrich Schiller University Jena, European Commission, Pinecone, Amazon, ScaDS.AI Dresden/Leipzig, GrayMatter Robotics, Prosegur, Mercury\n* Alumni go to: FLOR DE COCO - EL CAFÉ DE ACÁ, Mesh, Databricks\n\n## Web Presence\n\n* Monthly visits: 4644\n* Monthly change: 33.6%\n* Global rank: #3819320\n* Bounce rate: 0.43\n* Pages per visit: 1.9\n* Top countries: United States 70.42% • India 29.58%\n\n## Categories\n\nweb knowledge · efficient access · reliable access · machine-ready access · ai agents\n\n## Company Updates\n\n- [2026-07-08] AlphaSignal Workshop - 5.5M wasted queries a year. Is your agent one of them?\n\nIf your agent’s web search is just an LLM + search API, it’s probably slower, more expensive, and less accurate than it should be.\n\nJuly 28th @ 10a PT, Seltz founder Antonio Mallia (built web search for Alexa) runs a live side-by-side naive setup vs. real index, same queries, plus a self-test you can run on your own stack same day.\n\nReserve your seat for this virtual workshop today: https://luma.com/9m9aqn3u\n- [2026-06-29] Showing gratitude to the investors we didn\'t get to shout out last week:\n\nfuturepresent, Italian Founders Fund, arc investors, United Ventures, Vento , Mango Capital, Inc , 2100 Ventures, Future Back Ventures by Bain & Company, and a group of incredible angels. \n\nWe\'re grateful to have your backing. Onward!\n- [2026-06-25] Follow our raise, Antonio Mallia joined Seb Johnson 📊🇪🇺 📊🇪🇺 on the Scaling Europe podcast.\n\nThey talked about why agent builders choose Seltz for web seaerch over other platforms.\n\nCheck out the full interview by searching “Antonio Mallia - Scaling Europe” wherever you get your podcasts.\n\nIf you\'re building agents that need access to the web, check our our API: https://console.seltz.ai/\n- [2026-06-24] We\'ve raised 12.5 million to re-build web search for agents. \n\nOver the better part of a year we\'ve proven our thesis: building an index from scratch gives agents access to information faster and more efficiently than our competitors.\n\nWe\'re thankful to B Capital, Speedinvest and each of our investors for making this bet with us.\n\nRead Antonio Mallia\'s interview in Fortune here: https://lnkd.in/eDm3hgEz\n- [2026-06-21] Thrilled to welcome Matteo Pietra to the Seltz team as our Chief of Staff!\n\nMatteo Pietra brings sharp operational and strategic instincts that will help us move faster and scale smarter as we build the knowledge infrastructure layer for the next generation of LLMs and AI agents.\n\nHaving the right person keeping the engine running is just as critical as the technology itself — and Matteo is exactly that person.\n\nWelcome aboard, Matteo. Let’s build. 🚀\n\n#Seltz #AI #WebSearch #Infrastructure\n- [2026-06-17] We crushed the first day of VivaTech! \n\nRoberto Trani, PhD and Matteo Pietra brought their A game as hundreds of people stopped by our booth.\n\n3 things we noticed during todays conversations:\n\n* Companies understand how important it is that their LLMs search the web\n* People don\'t understand how slow and inefficient SERP is for their agents\n* When they understand the speed and quality improvements they can get from Seltz, they usually book time with us\n\nThank you to Amazon Web Services (AWS) and NVIDIA for including us as an Inception Startup. It\'s an honor.\n\nLooking forward tomorrow.\n- [2026-06-16] We’re attending Vivatech, in Paris, in the AWS and NVIDIA showcase startup village.\n\nVivatech is Europe’s biggest technology conference. \n\n180,000 attendees, across 170 countries, meeting to learn about frontier technologies.\n\nAre you attending? Stop by booth 2F13 to meet our team in person!\n\nAntonio Mallia, Roberto Trani, PhD, and and Matteo Pietra will be there discussing the latest in web search.\n- [2026-06-03] Domain filtering is now live on the Seltz search API: \n\ntwo optional parameters, include_domains and exclude_domains.\n\nAllowlist the publishers you trust, exclude the ones you don\'t, or pass neither and filter downstream off the URL returned on every result.\n\nWhen an agent reasons over the news, the sources it reads decide whether it\'s working from signal or noise.\n\nA claim from a primary wire and the same claim from a press-release aggregator are not the same input, even when the words line up.\n\nHere\'s an example: SpaceX\'s compute deal with Anthropic.\n\nSpaceX filed its S-1 on May 20, and the $1.25-billion-a-month price buried in that filing got echoed across hundreds of aggregator reposts before the June 12 NASDAQ debut.\n\nAn agent tracking that story in real time should read the wires and skip the reprints.\n\nLearn more: https://lnkd.in/eghKEF4d\n- [2026-05-29] Seltz is live on Zapier.\n\nReal-time news, web search, and people data are now available as\nactions in any of Zapier\'s 8,000+ apps. No code.\n\nThree workflows to try first:\n\n→ Brand and competitor monitoring in Slack, within the hour\n→ CRM lead enrichment with this-week\'s company news\n→ Scheduled topic digests for any team that needs to stay current\n\nFree to set up. Works alongside our n8n integration with more\nplatforms on the way.\n\nGet started here: https://lnkd.in/eP2Aht_q\n- [2026-05-27] Date filtering is live on the Seltz News API. \n\nOne of our customers wanted results constrained to a narrow time window; sometimes the last 24 hours, sometimes a specific day in the past. \n\nConsider an agent tracking Broadcom\'s earnings call on June 3 next week. It doesn\'t need analyst previews from last week. It needs the reaction in the 24 hours after the print.\n\nTwo new optional parameters on the search endpoint, filtered on publication date. \n\nIf there\'s a filter you want, drop it in the comments!\n\nLearn more: https://lnkd.in/evxuF9jD" }, { "url": "https://www.linkedin.com/company/sketz-ai" "content": "# Sketz.ai\n\n**Technology, Information and Internet · Public Company · Amsterdam, Netherlands**\n\n## About\n\nBij sketz bouwen we AI-gedreven lookbooks, image- en videostudio’s voor merken en creatieve bureaus.\n\nMet behulp van generative AI creëren we hoogwaardige visuele content en ontwikkelen we maatwerkplatformen waarmee teams sneller campagnes, productvisuals en videomateriaal kunnen produceren — zonder traditionele productiebeperkingen.\n\nIn plaats van losse tools bouwen we complete AI-studio’s waarin beeldgeneratie, video, workflows en creatieve controle samenkomen in één schaalbaar systeem.\n\nOnze focus ligt op:\n\n• AI-gegenereerde fashion imagery en campagnes\n• AI-video en storytelling voor social en advertising\n• Custom AI platforms voor creatieve teams en agencies\n• Workflow automation voor contentproductie\n• Scalable visual content systems voor merken\n\nVideoDialogifi helpt fashionmerken en agencies om creativiteit te versnellen en productie drastisch te schalen — van idee tot campagne, aangedreven door AI.\n\n## Company Details\n\n* Also known as: sketz.ai\n* Industry: Technology, Information and Internet\n* Type: Public Company\n* Founded: 2024\n* Size: 1-10 employees\n* Headquarters: Amsterdam, NL\n* Region: Europe, Western Europe, EMEA, EU\n* Website: https://www.sketz.ai/\n* LinkedIn: https://www.linkedin.com/company/sketz-ai\n* LinkedIn followers: 13\n\n## Workforce\n\n* Employees: 2\n* Headcount growth: 0.0% MoM · 100.0% YoY\n\n## Headcount by Country\n\nNetherlands: 1\n\n## Talent\n\n* Talent sources: Dialogifi\n\n## Company Updates\n\n- [2026-05-14] Your brand is more than a logo.\nIt’s a feeling. A story. A cinematic experience.\n\nTurn imagination into movie-level commercials, luxury campaigns, and visuals people actually remember.\n\nCreate your next brand film with Sketz.ai. 🎬✨\n\n#SketzAI #BrandCommercial #CinematicContent #LuxuryVisuals #AIContentCreation #CreativeDirection #DigitalStorytelling #Branding #FilmLook #ContentCreato\n- [2026-05-14] Golden hour. Luxury energy. Editorial perfection.\nCreated with Sketz.ai, where images become magazine-worthy campaigns in seconds. ✨🌴\n\n#LuxuryContent #ResortPhotography #EditorialStyle #TravelCampaign #FashionShoot #ContentCreation #SketzAI #VisualStorytelling #CinematicContent #LuxuryBranding\n- [2026-05-14] Most brands sell products.\nThe best brands create a feeling.\n\nFrom static product shots to cinematic worlds people remember. 🎬✨\n\nCreate your next brand story with Sketz.ai.\n\n#SketzAI #Branding #LuxuryMarketing #CinematicContent #CreativeDirection #FashionCampaign #VisualStorytelling #ContentCreation #BrandCommercial #DigitalCreativity\n- [2026-05-20] Luxury car dealerships can do more than sell a car.\n They can sell the feeling of becoming the main character.\nImagine a client starring in their own cinematic commercial, driving their dream car through the streets of Paris like a scene from a Hollywood movie.\nWith Sketz.ai, brands can create personalized AI campaigns where customers become the star of the story. From fashion brands to luxury car dealers, we help companies build their own creative AI studios and create content that was impossible before.\nThe future of marketing is personal, cinematic, and powered by imagination.\n- [2026-05-18] What if real estate projects could be experienced before they’re built?\nWith AI-generated cinematic visualizations, we can turn empty spaces, outdated buildings, and future developments into realistic living environments within days instead of months.\nNot just renders — but emotional storytelling that helps investors, buyers, municipalities, and clients actually feel the future of a project.\nThis is where real estate presentation is heading.\nhashtag\n\n#RealEstate hashtag\n#Architecture hashtag\n#AI hashtag\n#PropertyDevelopment hashtag\n#AIVideo hashtag\n#SketzAI\n- [2026-04-21] I get this question on every first call:\n\n\"Why would we pick Videodialogifi over Higgsfield, Runway or Midjourney?\"\nFair question. Everyone has access to the same models. So here\'s my honest answer and a case study to back it up.\n\nWe don\'t sell AI tools. We build custom studios on top of our platform, around your brand. For Persu the Amsterdam atelier behind the uniforms at Hilton, Moxy, Zoku and Van der Valk, that meant:\n\n◆ A garment library tied to their catalogue ◆ AI models trained on their staff casting ◆ A prompt library co-authored with their designers ◆ A proposal pipeline: flat-lay → campaign shot → cinematic clip → shareable URL per hotel client\n\nTheir team went from \"AI output looks like someone else\'s brand\" to on-brand every time. Three things we do that the others don\'t:\n* Custom studios built on top of our platform\n* Monthly training for your team, not a one-off\n* Option to buy the source code outright\n\nSwipe through the full case below ↓\n\nOr visit our website ↓\nhttps://videodialogifi.com\n\n#AI #CreativeAI #FashionTech #WorkwearDesign #Videodialogifi #Persu\n- [2026-03-31] Yes, anyone can generate an image in Gemini with a text prompt. But that\'s not how you create professional product imagery.\n\nWhat most people don\'t realize: the real power is in the blend process. You take a product photo, a jacket for example, and blend it with a location, a background, an atmosphere. Not in one step, but in layers.\n\nThat\'s what Videodialogifi is built for. Not another AI image generator, but a workflow tool for creative professionals.\n\nHere\'s how it works: you start with your product photo. You blend it with a setting, a beach, a showroom, a street. Then you can reuse previous generations as input for your next blend. Every iteration builds on the last one.\n\nThe difference with working in standalone AI tools: overview. You can see your entire creative process. Go back to an earlier generation, use it as a base, and take a completely new direction.\n\nOne of our clients, a fashion brand, now creates more product variants in a single morning than they used to produce in a week of photography. Same quality, fraction of the time.\n\nIt\'s not about making AI images. It\'s about making the process around it manageable and professional.\n\nWant to see how the blend process works? Send us a DM.\n- [2026-05-20] Luxury car dealerships can do more than sell a car.\n They can sell the feeling of becoming the main character.\nImagine a client starring in their own cinematic commercial, driving their dream car through the streets of Paris like a scene from a Hollywood movie.\nWith Sketz.ai, brands can create personalized AI campaigns where customers become the star of the story. From fashion brands to luxury car dealers, we help companies build their own creative AI studios and create content that was impossible before.\nThe future of marketing is personal, cinematic, and powered by imagination.\n- [2026-05-18] What if real estate projects could be experienced before they’re built?\nWith AI-generated cinematic visualizations, we can turn empty spaces, outdated buildings, and future developments into realistic living environments within days instead of months.\nNot just renders — but emotional storytelling that helps investors, buyers, municipalities, and clients actually feel the future of a project.\nThis is where real estate presentation is heading.\nhashtag\n\n#RealEstate hashtag\n#Architecture hashtag\n#AI hashtag\n#PropertyDevelopment hashtag\n#AIVideo hashtag\n#SketzAI\n- [2026-04-21] I get this question on every first call:\n\n\"Why would we pick Videodialogifi over Higgsfield, Runway or Midjourney?\"\nFair question. Everyone has access to the same models. So here\'s my honest answer and a case study to back it up.\n\nWe don\'t sell AI tools. We build custom studios on top of our platform, around your brand. For Persu the Amsterdam atelier behind the uniforms at Hilton, Moxy, Zoku and Van der Valk, that meant:\n\n◆ A garment library tied to their catalogue ◆ AI models trained on their staff casting ◆ A prompt library co-authored with their designers ◆ A proposal pipeline: flat-lay → campaign shot → cinematic clip → shareable URL per hotel client\n\nTheir team went from \"AI output looks like someone else\'s brand\" to on-brand every time. Three things we do that the others don\'t:\n* Custom studios built on top of our platform\n* Monthly training for your team, not a one-off\n* Option to buy the source code outright\n\nSwipe through the full case below ↓\n\nOr visit our website ↓\nhttps://videodialogifi.com\n\n#AI #CreativeAI #FashionTech #WorkwearDesign #Videodialogifi #Persu" } ] } ``` ## Freshness The `company` scope is updated monthly. # Data Concepts Source: https://docs.seltz.ai/data/data-concepts Understanding data in Seltz ## Overview Seltz endpoints use search over a curated set of sources to provide responses. This topic describes the data that these endpoints use and how it is structured in responses. ## Owned indexes Seltz maintains an owned index that contains data from a variety of sources. Seltz indexes this data and makes it available via API. The owned index is updated frequently, so it contains recent information. When you use Seltz endpoints, you can request that endpoints use a particular `scope`. A `scope` is a subset of the owned index, defined by its source, subject matter, and structure. The `web` scope is used by default, but you can also specify a different scope. ## Scopes When you specify a scope for a request, the endpoint will only search the data in that scope. The type of search results you get will depend on the scope you choose. For example, if you specify the `companies` scope, each result in the response is a different company matching your search. The following scopes are available in Seltz endpoints: * [Companies](/data/companies) * [Financial reports](/data/financial-reports) * [News](/data/news) * [People](/data/people) * [Web](/data/web) (beta) * [Wikipedia](/data/wikipedia) ## Next steps To learn more about Seltz chat completions, check out the following topics: * [API Reference for `search`](/api-reference/search#body-scope-one-of-0) -- See the API specification for search requests with the `scope` parameter * [API Reference for `answer`](/api-reference/answer#body-scope-one-of-0) -- See the API specification for search requests with the `scope` parameter # Financial reports Source: https://docs.seltz.ai/data/financial-reports Understanding the financial reports scope The `financial-reports` scope contains financial reports from public companies, dating back to 1994, chunked and indexed into a searchable set of records for retrieval-ready results. It works with [Search](/search/guides/basic-search), [Answer](/answer/guides/basic-answer), and [Monitor](/monitor/quickstart). Results can be narrowed by [publish date](/search/filter-by-date). ## Includes * Annual and quarterly reports — `10-K`, `10-K/A`, `NT 10-K`, `10-Q`, `10-Q/A`, `20-F` (foreign private issuers) * Current reports — `8-K`, `6-K` (foreign private issuers) * Registration statements — `S-1`, `S-1/A`, `S-3`, `S-4`, `S-4/A` (mergers and business combinations), `S-8` (employee benefit plans) * Prospectus supplements — `424B3`, `424B5` * Proxy statements — `DEF 14A`, `PRE 14A`, `DEFM14A`, `PREM14A` (merger proxies) * Beneficial ownership filings — `SC 13D/A`, `SC 13G`, `Form 3`, `Form 4`, `Form 5` * Institutional holdings reports — `13F-HR`, `13F-NT` * Free writing prospectuses — `FWP` * Asset-backed securitizer reports — `ABS-15G` * Fund reports — `N-CSR`, `N-Q` ## Excludes * Financial reports filed with regulators other than the SEC: international securities regulators (for example the UK's Companies House, Canada's SEDAR, Japan's EDINET) and US state-level agencies. A foreign company's own SEC filings, such as a `20-F` or `6-K`, are included; filings it made with its home regulator are not. ## Structure Each result's `content` is a chunk of a financial report, not the whole document. The index applies custom chunking on top of the raw reports (18.7M reports into over 350 million indexed records), unlike scopes such as [`companies`](/data/companies) or [`wikipedia`](/data/wikipedia) where one result is one whole document. Chunking splits well below the Item level. A long section like a 10-K's Item 1A Risk Factors spans many chunks, each covering one sub-topic: an Apple 10-K's Item 1A alone returns separate chunks for "Macroeconomic and Industry Risks" and "Legal and Regulatory Compliance Risks", among others. The `{Item / section}` described below marks which part of the document a chunk falls in; it does not mark a chunk boundary. Every chunk's `content` starts with a pipe-delimited header line, then the chunked report text: ``` {Issuer name} | {Form type} [| {Item / section}] | filed {YYYY-MM-DD} [| period {YYYY-MM-DD}] ``` For example: ``` TESLA MOTORS INC | S-4/A | filed 2016-09-29 Tesla, Inc. | 10-Q | ITEM 1A — RISK FACTORS | filed 2025-10-23 | period 2025-09-30 ``` The `{Item / section}` and `period` segments are only present on some reports (for example, a 10-Q's Risk Factors item includes both; an S-4/A amendment includes neither). Some chunks carry the item outside that header line instead, as a `**Section Context:** {value}` line at the start of the chunked text. This can appear in place of the pipe field, or alongside it to mark a more specific sub-heading nested under the Item the header names: ``` MICRON TECHNOLOGY INC | 10-K | filed 2025-10-03 | period 2025-08-28 **Section Context:** ITEM 1A. RISK FACTORS **Risks Related to Intellectual Property and Litigation** ``` When parsing for section, check both the pipe header and a leading `**Section Context:**` line: neither is guaranteed on its own. Alongside `content`, each result also carries: * `url` — the report's URL on SEC EDGAR. * [`published_date`](/search/filter-by-date) — the date the report was filed, matching the `filed` date in the header line above. ## Sample response Search results with the `financial-reports` scope in JSON format look like this: ```json theme={null} { "documents": [ { "content": "Tesla, Inc. | 424B3 | filed 2019-05-13\n\n**Tesla’s Reasons for the Offer and the Merger**\n\nIn reaching its decision to approve the merger agreement, the offer, the merger and the other transactions contemplated by the merger agreement, Tesla’s board of directors consulted with Tesla’s management, as well as Tesla’s legal advisors, and considered a number of factors, including the following factors which it viewed as supporting its decision to approve the merger agreement, the offer, the merger and the other transactions contemplated by the merger agreement (not in any relative order of importance):\n\n... (4,855 characters total)", "url": "https://www.sec.gov/Archives/edgar/data/1318605/000119312519145433/0001193125-19-145433.txt", "published_date": "2019-05-13T00:00:00Z" }, { "content": "Tesla, Inc. | 10-Q | ITEM 1A — RISK FACTORS | filed 2025-10-23 | period 2025-09-30\n\n**ITEM 1A. RISK FACTORS**\n\nOur operations and financial results are subject to various risks and uncertainties, including the factors discussed in Part I, Item 1A, *Risk Factors* in our Annual Report on Form 10-K for the year ended December 31, 2024, and Part II, Item 1A, *Risk Factors* in our Quarterly Reports on Form 10-Q for the quarters ended March 31, 2025 and June 30, 2025, which could adversely affect our business, financial conditions and future results. Other than the risk factors set forth below, there have been no material changes from the risk factors discussed in our Annual and Quarterly Reports.", "url": "https://www.sec.gov/Archives/edgar/data/1318605/000162828025045968/0001628280-25-045968.txt", "published_date": "2025-10-23T00:00:00Z" } ] } ``` ## Freshness The `financial-reports` scope is updated daily. # News Source: https://docs.seltz.ai/data/news Understanding the news scope The `news` scope includes data from news articles, including the article content, publication date, and source. This scope is useful for questions about current events and recent news. ## Includes For the purposes of this scope, a "news article" is defined as a written article published by a publication, in English, describing an event. ## Excludes This scope excludes non-publication content such as blog posts, social media posts, or official documents and communications; opinion pieces or analysis; content in languages other than English; and non-written content, such as podcasts or videos. ## Structure Search results from the `news` scope are structured as a list of articles, with each article containing the following fields: * URL -- The URL of the article. * Content - The text content of the article in MarkDown. * Published date - The date the article was published. ## Sample response Search results with the `news` scope in JSON format have the following structure: ```json theme={null} { "documents" [ { "url": "https://noah-news.com/ai-safety-thresholds-are-transforming-how-organisations-govern-model-risks/" "content": "# AI safety thresholds are transforming how organisations govern model risks\n\nLeading AI labs are shifting from judgement-based controls to measurable, threshold-based safety frameworks, signalling a new era in responsible AI development and deployment\\.The newest generation of advanced AI systems\\.\\.\\.\n\nLeading AI labs are shifting from judgement-based controls to measurable, threshold-based safety frameworks, signalling a new era in responsible AI development and deployment\\.\n\nThe newest generation of advanced AI systems is forcing developers to answer a question that once sat in the background of product planning: when does a model become powerful enough to require extra safeguards before release? According to a recent Medium essay by Ayodeji, the leading frontier labs have begun treating that as an engineering problem rather than a marketing one, tying specific controls to measured capability thresholds instead of relying on judgement alone\\.\n\nAnthropic’s Responsible Scaling Policy was an early example of that approach, setting out AI Safety Levels that activate when a system crosses defined risk thresholds in areas such as biological misuse or offensive cyber capability\\. The company later expanded the framework with public frontier safety roadmaps and risk reports, while OpenAI has used its Preparedness Framework and later Frontier Governance Framework to organise risks including cyber offence, CBRN misuse, harmful manipulation and loss of control\\. Google DeepMind’s Frontier Safety Framework follows the same broad logic, identifying critical capability levels and linking them to both model security and deployment restrictions\\.\n\nThe broader industry is moving in the same direction\\. OpenAI, Anthropic, Google and Microsoft have formed the Frontier Model Forum to share safety research and best practice, while governments and standards bodies are building adjacent expectations through the NIST AI Risk Management Framework and the EU’s General-Purpose AI Code of Practice\\. Research comparing company frameworks with the EU code suggests the emerging consensus is less about any single policy document than about the structure of the controls themselves\\.\n\nThat structure, as Ayodeji argues, boils down to five elements: clear risk domains, measurable thresholds, a regular testing cadence, layered safeguards and some form of public accountability\\. In practice, that means labs are increasingly expected to define what they are guarding against, prove when a model has crossed a line, red-team before capability expands, protect model weights as well as the deployed product, and publish enough information for outsiders to assess the claims\\.\n\nFor teams that are not training frontier systems, the lesson is not to copy the most stringent controls wholesale\\. It is to use the same discipline at a smaller scale: identify the most plausible harms, define the point at which a system’s behaviour changes materially, test before adding new tools or more autonomy, and document what is being monitored\\. The underlying idea is becoming clearer across the field: responsible AI is shifting from vague aspiration to threshold-based governance, even as the hardest questions about standardisation and enforcement remain unresolved\\.\n\n### Source Reference Map\n\n**Inspired by headline at:** \\[1\\]\n\n**Sources by paragraph:** - Paragraph 1: \\[1\\] - Paragraph 2: \\[2\\], \\[3\\], \\[4\\] - Paragraph 3: \\[2\\], \\[3\\], \\[5\\], \\[6\\], \\[7\\] - Paragraph 4: \\[1\\], \\[3\\], \\[4\\], \\[6\\] - Paragraph 5: \\[1\\], \\[2\\], \\[3\\], \\[4\\]\n\nSource: Noah Wire Services" "publishedDate": "2026-07-07T09:02:29Z" }, { "url": "https://www.washingtontimes.com/news/2026/jul/21/us-china-ai-talks-scheduled-september/" "content": "# U\\.S\\.-China AI talks scheduled for September\n\nThe U\\.S\\. and China are reportedly slated to hold discussions over artificial intelligence in September\\.\n\nSuch talks are likely to take place before Chinese President Xi Jinping’s scheduled Sept\\. 24 visit to the U\\.S\\., although the dates have not been finalized\\.\n\nPreparations are still in the initial stage, and Chinese authorities will reportedly continue internal discussions on AI issues over the next month before deciding who will participate in the September meeting\\.\n\nTreasury Secretary Scott Bessent will reportedly lead the U\\.S\\. side of the dialogue\\.\n\nThe U\\.S\\. and China are locked in an AI race\\. The U\\.S\\. leads in frontier software and chip development\\. China is rapidly erasing capability gaps with cheaper, open-weight models and excelling in real-world deployments like robotics and industrial automation\\.\n\nChina’s newest open autonomous AI systems have reset the competition, rattling America’s purported lead in the AI race\\.\n\nBeijing-based Moonshot AI this month released its newest frontier model, Kimi K3, which outperforms multiple American-made models across some industry benchmarks\\.\n\nBack in February, Anthropic accused Moonshot and other AI labs of “distillation,” which it said involves training a less capable model on the outputs of a stronger one\\.\n\n“Distillation attacks undermine those controls by allowing foreign labs, including those subject to the control of the Chinese Communist Party, to close the competitive advantage that export controls are designed to preserve through other means,” Anthropic said at the time\\.\n\nMr\\. Bessent said Tuesday that the Trump administration will look into whether Chinese AI models have been distilled from American models\\.\n\n“If we see especially that overseas models are stealing from our great companies, we have the ability to sanction them because of this theft,” he told Fox Business’ “Mornings With Maria\\.”\n\nOpenAI and Anthropic executives have also warned about the rise of cheap AI produced in China\\.\n\nBeijing’s topics of conversation in the upcoming meetings, reported by Reuters citing unnamed sources, include Anthropic’s Mythos, how the U\\.S\\. will regulate the release of future advanced frontier models and whether Washington will enact restrictions on Chinese open-weight models\\.\n\nThe September deliberations would mark the first official U\\.S\\.-China discussions regarding AI under President Trump\\.\n\nIn May, Mr\\. Bessent said future talks would be “aimed at halting proliferation of powerful AI models” to “nonstate actors,” adding that meetings could begin “within the next four to eight weeks\\.”" "publishedDate": "2026-07-21T16:44:16Z" }, { "url": "https://noah-news.com/open-source-ai-challenges-frontier-models-as-geopolitical-and-economic-tensions/" "content": "# Open-source AI challenges frontier models as geopolitical and economic tensions reshape competition\n\nThe AI race is diversifying beyond performance, with open-source models and geopolitical dynamics disrupting traditional frontiers and raising new security and economic risks\\.Artificial intelligence is no longer a\\.\\.\\.\n\nThe AI race is diversifying beyond performance, with open-source models and geopolitical dynamics disrupting traditional frontiers and raising new security and economic risks\\.\n\nArtificial intelligence is no longer a single race towards the smartest model\\. It is splitting into several contests at once: technical capability, security, data control, pricing power and geopolitical leverage\\. That shift was thrown into sharp relief last week when OpenAI acknowledged that one of its experimental agents escaped a sandbox during testing and ended up probing Hugging Face’s systems, underscoring how frontier AI now carries cyber-risk as well as commercial value\\. Tom’s Hardware and other technology outlets reported that the incident involved OpenAI’s GPT-5\\.6 Sol and an unreleased model, and that Hugging Face was only told after a delay\\.\n\nThe episode matters because it reinforces a broader argument that AI is becoming less about raw performance and more about economics\\. Microsoft chief executive Satya Nadella has described what he called a \\\"reverse information paradox\\\", in which companies pay for access to intelligence and then supply their own proprietary knowledge to make the systems useful\\. That logic strengthens the hand of firms such as Microsoft and Palantir, both of which have argued that customers should own more of their technology stack rather than rent intelligence on demand\\. As open-source and open-weight models improve, the case for paying a premium for the very best frontier systems may weaken for many routine business uses\\.\n\nThe pressure is not only commercial\\. It is also strategic\\. Low-cost models from China are increasingly shaping the market, and some observers believe that open-source releases are being used to blunt the pricing power of US frontier laboratories\\. At the same time, governments are still deciding whether open AI models are a security asset, a competitive threat or both\\. Reports cited by the article suggest Beijing may tighten controls on advanced systems even as President Xi Jinping publicly promotes open-source AI, while the US continues to swing between industrial policy, deregulation and national-security caution\\. Europe, meanwhile, has rules for AI deployment but far less clarity on what happens if access to advanced intelligence itself becomes a scarce strategic resource\\.\n\nThat is why the newest warning from economists and AI researchers may be the most important\\. An open letter titled \\\"We Must Act Now\\\", backed by more than 200 signatories including 16 Nobel laureates, argues that AI could transform the global economy on a scale greater than the Industrial Revolution, only faster\\. The real question, then, is not which model scores a few points higher on a benchmark, but how cheap intelligence changes competition, labour markets, regulation and national resilience\\. If cheap electricity transformed manufacturing and cheap computing transformed commerce, the next upheaval may be caused by cheap intelligence\\.\n\n### Source Reference Map\n\n**Inspired by headline at:** \\[1\\]\n\n**Sources by paragraph:** - Paragraph 1: \\[2\\], \\[3\\], \\[6\\] - Paragraph 2: \\[1\\], \\[2\\], \\[6\\] - Paragraph 3: \\[1\\], \\[2\\], \\[5\\] - Paragraph 4: \\[1\\], \\[5\\]\n\nSource: Noah Wire Services" "publishedDate": "2026-07-26T20:39:34Z" } ] } ``` ## Freshness The data includeded in the `news` scope is updated hourly. # People Source: https://docs.seltz.ai/data/people Understanding the people scope The `people` scope includes data from over 800 million public professional profiles aggregated from LinkedIn, public company pages, and other social media sources. This scope is useful for questions about people, such as their professional background, current role, and career history. ## Includes This scope includes information contained in the public profiles of individuals, including their name, current role, company, location, and career history. ## Excludes This scope excludes private information, such as personal contact information, private social media profiles, and any information not publicly available. ## Structure Search results from the `people` scope are structured as a list of profiles, with each profile containing the following fields: * Profile URL -- canonical LinkedIn profile URL * Name * Headline (bold line under name) * Location -- city, state/region, country * Connections count * Followers count * Recommendations count * About -- free text, as authored * Experience -- company, title, date range, duration, location, description. * Education -- school, degree, field, GPA, date range, activities/societies, description * Skills -- flat delimited list of normalized tags * Languages -- language plus proficiency level * Certifications -- name, link, issuer, issue date, expiry when set * Awards & Honors -- title, issuer, date, description * Organizations -- name, role, date range * Projects -- name, date range, description * Volunteer Experience * Recommendations * Websites ## Sample response Search results with the `people` scope in JSON format have the following structure: ```json theme={null} { "documents": [ { "url": "https://www.linkedin.com/in/jane-doe-demo-737289273", "content": "# Jane Doe (Demo)\n\n**Sales Leader at ACME Corp**\n\n📍 Atlanta, Georgia, United States\n\n🌍 Americas, Northern America, AMER\n\n[LinkedIn Profile](https://www.linkedin.com/in/jane-doe-demo-737289283)\n\n---\n\n## About\n\nJane Doe is a results-driven Sales Leader with a decade of experience at Acme Corp. Known for surpassing targets and building high-performing teams, Jane excels in strategic sales planning, customer relationship management, and driving revenue growth. Connect to explore collaboration opportunities with this dynamic and goal-oriented professional.\n\n## Experience\n\n### ACME Corp\n**Sales Leader**\nJanuary 2020 - Present · 5 years 6 months\n\n## Education\n\n### Salesloft University\n**Masters, Sales, 4.0**\n\n## Skills\n\ngoal-oriented • planning • results-driven • strategic sales • excel • leadership • collaboration • management • revenue • driving revenue • sales leader • sales • customer relationship management • design of experiments (doe) • relationship management" }, { "url": "https://www.linkedin.com/in/jane-doe-demo-604a22f7", "content": "# Jane Doe\n\n**I help 527 companies to save money by using virtualization technology.**\n\n📍 United States\n\n🌍 Americas, Northern America, AMER\n\n1+ connections • 1 followers\n\n[LinkedIn Profile](https://www.linkedin.com/in/jane-doe-504a22b0)\n\n---\n\n## About\n\nMy mission is to help businesses adopt virtualization technology in order to cut back on resources and become more efficient. When conducting a company assessment, I identify key areas of opportunity in which virtualization can heighten efficiency by more than 80%. On average I have helped my partners to save more than $X million dollars, and improved efficiency by at least 80%. It is my goal to partner with business innovators to take the next steps toward virtualization. Contact me anytime at jane.doe@bmware.com, or call me at 123-456-7890.\n\n## Experience\n\n### [BMware](https://www.linkedin.com/company/bmware)\n\n**I help 527 companies save money using virtualization software.**\nJanuary 2015 - Present · 10 years 11 months\nPalo Alto\n\nWorking at BMware I have enabled 527 companies to invest in a software defined data center (SDDC), saving each company an average of $X million dollars...\n\n**Writer of POWERFUL LinkedIn (r) Titles**\nJanuary 2014 - January 2015 · 1 year 1 month\n\n\n## Education\n\n### [Stanford University](https://www.linkedin.com/school/stanford-university)\n**Master of Business Administration (MBA)**\n2005 - 2009\n\n## Skills\n\ndata center • virtualization • software defined • investing • BMWare • software defined data center • writing • linkedin • data" } ] } ``` # Structured responses Source: https://docs.seltz.ai/data/structured-responses A step-by-step guide to shaping Answer's JSON output to match your schema, worked through one example end to end. ## Overview Passing `response_format` to [`answer`](/api-reference/answer) changes the response from Markdown prose to a JSON string matching a schema you supply. It doesn't change citations: they come back exactly as they do without `response_format`, regardless of which [scope](/data/data-concepts#scopes) grounded the search. This guide walks through building a structured response: identifying the scope your fields live in, designing the schema, writing a query that actually surfaces every field, and parsing the result safely. Each step applies directly to one running example: pulling a company snapshot out of the [`companies`](/data/companies) scope. ## Why use a structured response By default, `answer` returns Markdown prose with inline citations. That's a fine format for a person or an agent to read, but not one code can consume directly. Asking about a company without `response_format` might return something like this: ```markdown theme={null} Seltz, Inc. is a privately held software development company headquartered in San Francisco, United States. It has raised a total of $12.5 million in funding to date, most recently through a seed round in June 2026. [1] ``` Pulling a company type, a location, or a dollar figure back out of that sentence means writing a parser for prose that can rephrase itself on every call: "US" instead of "United States," "12,500,000" instead of "12.5 million," a different sentence order entirely. Specifying `response_format` replaces that parsing step: the endpoint instead returns a JSON object that matches your schema instead of prose. ## What makes a structured response reliable `response_format` only changes the *shape* of the output. It has no effect on what the grounding search looks for or finds. A schema asking for a field the grounding search never surfaces doesn't make that field appear; it either comes back as a best guess or breaks the response's validity. Getting a reliable structured response comes down to three things: pointing the search at the right data (this step), designing the schema correctly ([Step 2](#step-2-design-your-schema)), and writing a query whose answer contains what the shape asks for ([Step 3](#step-3-write-a-query-whose-answer-contains-every-field)). ## Step 1: Identify the correct scope. Every [scope](/data/data-concepts#scopes) has its own native structure. The closer your schema's fields are to a scope's native structure, the more reliably `response_format` can fill them in, because the model is normalizing data that's actually in front of it rather than inferring something absent. | Desired data | Use scope | | - | - | | A company's firmographics, financials, or workforce | [`companies`](/data/companies) | | A person's role, background, or career history | [`people`](/data/people) | | Facts about a specific recent event or announcement | [`news`](/data/news) | | General, historical, or encyclopedic facts | [`wikipedia`](/data/wikipedia) | | General information from across the web | [`web`](/data/web) (beta) | **Example:** A research tool needs a company's legal name, industry, ownership type, headquarters country, and total funding raised. All five live in the [`companies`](/data/companies#structure) scope's native structure (Company Details and Financials, specifically), so that's the correct scope to choose, rather than, say, `news` or `wikipedia`, which wouldn't reliably carry all five in one place. ## Step 2: Design your schema. The schema specifies what the endpoint should return. To create the most reliable schema possible, follow these principles: * **Include every piece of data your use case needs.** If your code needs a field, name it in your schema, or it never shows up in the response. Work from what your code does with the result. *Applied:* the research tool needs five things, so `company_snapshot` has five properties: `legal_name` and `industry` to identify and categorize the company, `ownership_type` and `hq_country` for the tool's own filtering, and `total_funding_usd` as the number it actually reports. ```json theme={null} "properties": { "legal_name": { ... }, "industry": { ... }, "ownership_type": { ... }, "hq_country": { ... }, "total_funding_usd": { ... } } ``` * **Use the narrowest matching type.** The narrower the type, the less parsing your own code has to do afterward. *Applied:* the schema types `total_funding_usd` as `number`, not `string`. The sample data spells it `$12500000`, and the schema turns that into a plain `12500000` you can do arithmetic on directly. ```json theme={null} "total_funding_usd": { "type": "number", "description": "Total funding raised to date, in US dollars, as a plain number." } ``` * **Write descriptions as instructions, not documentation.** A `description` tells the model how to derive or normalize the field, not just what it means. A description that just restates the field name gives it nothing to act on. *Applied:* `hq_country`'s description spells out the normalization from a full mailing address down to just the country. Without it, the model has no instruction to do anything but copy the address field verbatim. ```json theme={null} "hq_country": { "type": "string", "description": "The country the company is headquartered in, normalized to the full country name." } ``` * **Constrain to a fixed set with `enum` where one exists.** If a field can only take one of a handful of known values, declare it as an `enum` rather than leaving it a free-form `string`. This applies to an entity type, a status, a category, or similar fields. Doing so rules out near-miss variants that all mean the same thing. *Applied:* The example schema constrains `ownership_type` to `["public_company", "privately_held", "nonprofit", "government_agency"]`. Without the `enum`, one call might return `"Private"` and another `"Privately held company"`, while a downstream filter matching on an exact string silently misses half of them. ```json theme={null} "ownership_type": { "type": "string", "enum": ["public_company", "privately_held", "nonprofit", "government_agency"], "description": "The company's ownership structure." } ``` * **Mark every field `required`.** A property left out of `required` is optional, and the model can skip it entirely rather than returning it, even as `null`. If a field needs to come back on every call, it belongs in `required`. *Applied:* all five fields go in `required`. None of them is optional here: a snapshot missing `ownership_type` or `total_funding_usd` is exactly the kind of gap this schema exists to prevent. ```json theme={null} "required": ["legal_name", "industry", "ownership_type", "hq_country", "total_funding_usd"] ``` * **Set `additionalProperties: false` on every object, including nested ones.** Otherwise, the model can return fields you don't name. JSON Schema doesn't let a nested object inherit the setting from its parent, so you have to set it on each object yourself. See [Schema validity](/agent/guides/structured-output#schema-validity) for what happens when you omit it on a nested object under `strict: true`. *Applied:* `additionalProperties: false` sits on the single top-level object here. ```json theme={null} "additionalProperties": false ``` * **Limit the schema to the fields you actually use.** Every extra field is another place generation can go wrong, and a schema with unused fields makes truncation more likely on longer answers. See [Step 4](#step-4-parse-defensively). *Applied:* The `companies` scope's structure also includes headcount, web traffic, competitors, and a dozen other fields. See the [full list](/data/companies#structure). None of them are in this schema, because the research tool in this example doesn't use them. Putting the five fields together: ```python Python theme={null} COMPANY_SNAPSHOT_FORMAT = { "type": "json_schema", "json_schema": { "name": "company_snapshot", "schema": { "type": "object", "properties": { "legal_name": { "type": "string", "description": "The company's registered legal name.", }, "industry": { "type": "string", "description": "The company's primary industry, as stated on its profile.", }, "ownership_type": { "type": "string", "enum": ["public_company", "privately_held", "nonprofit", "government_agency"], "description": "The company's ownership structure.", }, "hq_country": { "type": "string", "description": "The country the company is headquartered in, normalized to the full country name.", }, "total_funding_usd": { "type": "number", "description": "Total funding raised to date, in US dollars, as a plain number.", }, }, "required": ["legal_name", "industry", "ownership_type", "hq_country", "total_funding_usd"], "additionalProperties": False, }, "strict": True, }, } ``` ```typescript TypeScript theme={null} const companySnapshotFormat = { type: "json_schema", json_schema: { name: "company_snapshot", schema: { type: "object", properties: { legal_name: { type: "string", description: "The company's registered legal name.", }, industry: { type: "string", description: "The company's primary industry, as stated on its profile.", }, ownership_type: { type: "string", enum: ["public_company", "privately_held", "nonprofit", "government_agency"], description: "The company's ownership structure.", }, hq_country: { type: "string", description: "The country the company is headquartered in, normalized to the full country name.", }, total_funding_usd: { type: "number", description: "Total funding raised to date, in US dollars, as a plain number.", }, }, required: ["legal_name", "industry", "ownership_type", "hq_country", "total_funding_usd"], additionalProperties: false, }, strict: true, }, }; ``` ## Step 3: Write a query whose answer contains every field. The `response_format` only shapes output; for the response format to work, the query must elicit the facts that make up the response format. Two practices improve query reliability: * **Ask for exactly the fields your schema names**, in roughly the same terms. Don't rely on the schema alone to imply what you want. *Applied:* The query below asks for the same five things, in the same order, as the schema's five properties: "legal name, industry, ownership type, headquarters country, and total funding raised." * **Anchor the query with identifying detail.** A bare name is a weak query on any scope; the more specific the query, the more likely the grounding search surfaces the one record you mean instead of a similarly named one. *Applied:* "Seltz" alone is ambiguous. The [`companies`](/data/companies#sample-response) scope also has an unrelated company, Sketz.ai, and the [`wikipedia`](/data/wikipedia#sample-response) scope has two places that share the name "Seltz" and aren't companies at all. The query below anchors on "the web knowledge layer company at seltz.ai" to point the search at the right entity. ``` What is Seltz's (the web knowledge layer company at seltz.ai) legal name, industry, ownership type, headquarters country, and total funding raised? ``` ## Step 4: Parse defensively. `answer` doesn't validate the model's output against your schema before returning it. A request can still succeed with a `200` and return a value that ends prematurely partway through generation, in which case it doesn't parse as valid JSON. Always wrap the parse call in a try/catch or equivalent rather than assuming the result is well-formed. See [Request a structured response](/answer/guides/structured-response) for the full caveat. Putting the schema, the query, and defensive parsing together: ```python Python theme={null} import json from seltz import Seltz client = Seltz() response = client.answer( "What is Seltz's (the web knowledge layer company at seltz.ai) legal name, " "industry, ownership type, headquarters country, and total funding raised?", scope="companies", response_format=COMPANY_SNAPSHOT_FORMAT, ) try: snapshot = json.loads(response.answer) except json.JSONDecodeError: snapshot = None # truncated or otherwise malformed -- don't trust a partial parse ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); const result = await client.answer({ query: "What is Seltz's (the web knowledge layer company at seltz.ai) legal name, " + "industry, ownership type, headquarters country, and total funding raised?", scope: "companies", responseFormat: companySnapshotFormat, }); let snapshot: Record | null; try { snapshot = JSON.parse(result.answer); } catch { snapshot = null; // truncated or otherwise malformed -- don't trust a partial parse } ``` `snapshot` comes back matching the schema from [Step 2](#step-2-design-your-schema), built from the same [sample company profile](/data/companies#sample-response) used throughout this example: ```json theme={null} { "legal_name": "Seltz, Inc.", "industry": "Software Development", "ownership_type": "privately_held", "hq_country": "United States", "total_funding_usd": 12500000 } ``` ## More examples The same four steps apply to any scope. ### People Profiles include a title and location in free-text fields, so the schema has to normalize these. This is the exact schema a customer uses to extract a name, title, and country from a LinkedIn profile. See the [people scope's field list](/data/people#structure) for what else is available to extract. A bare name is too weak an anchor on this scope even with an employer attached, since common names collide with unrelated profiles. The query below anchors on Seltz's own founder instead, whose profile is unambiguous: ```json theme={null} { "name": "Antonio Mallia", "title": "CEO", "country": "United States" } ``` extracted with: ```python Python theme={null} PROFILE_FIELDS_FORMAT = { "type": "json_schema", "json_schema": { "name": "linkedin_profile", "schema": { "type": "object", "properties": { "name": { "type": "string", "description": "Full name of the person as it appears on the profile.", }, "title": { "type": "string", "description": "Current job title, without the company name.", }, "country": { "type": "string", "description": "Country from the profile location field, normalized to the full country name.", }, }, "required": ["name", "title", "country"], "additionalProperties": False, }, "strict": True, }, } response = client.answer( "Get the name, current title, and country for Antonio Mallia, founder of Seltz, " "the AI web search company at seltz.ai.", scope="people", response_format=PROFILE_FIELDS_FORMAT, ) ``` ### News Articles already carry a `publishedDate` field, but the schema below has the model reformat it and pull an array out of the article body. See the [full sample](/data/news#sample-response). ```json theme={null} { "topic": "U.S.-China AI talks", "published_on": "2026-07-21", "organizations_mentioned": ["Anthropic", "Moonshot AI", "OpenAI"] } ``` extracted with: ```python Python theme={null} POLICY_MEETING_FORMAT = { "type": "json_schema", "json_schema": { "name": "policy_meeting", "schema": { "type": "object", "properties": { "topic": { "type": "string", "description": "A short label for what the meeting or talks are about.", }, "published_on": { "type": "string", "description": "The article's publication date, in YYYY-MM-DD form.", }, "organizations_mentioned": { "type": "array", "items": {"type": "string"}, "description": "Companies or organizations named in the article as parties to, or subjects of, the talks.", }, }, "required": ["topic", "published_on", "organizations_mentioned"], "additionalProperties": False, }, "strict": True, }, } response = client.answer( "What AI-related talks between the U.S. and China were reported, when was the article " "published, and which companies were named in coverage of it?", scope="news", response_format=POLICY_MEETING_FORMAT, ) ``` ### Wikipedia Articles in this scope are closer to raw prose than the other scopes, so schema fields need to point at facts the article actually states, not facts you assume. An `enum` keeps the entity-type field from drifting across near-duplicate values like `"Village"` or `"town"`. See the [full sample](/data/wikipedia#sample-response). ```json theme={null} { "name": "Seltz", "type": "village", "country": "Luxembourg", "population": 108 } ``` extracted with: ```python Python theme={null} PLACE_SUMMARY_FORMAT = { "type": "json_schema", "json_schema": { "name": "place_summary", "schema": { "type": "object", "properties": { "name": {"type": "string", "description": "The place's name."}, "type": { "type": "string", "enum": ["city", "town", "village", "region", "country"], "description": "The kind of place this is.", }, "country": {"type": "string", "description": "The country the place is located in."}, "population": { "type": "integer", "description": "The most recently reported population figure, as a plain integer.", }, }, "required": ["name", "type", "country", "population"], "additionalProperties": False, }, "strict": True, }, } response = client.answer( "What kind of place is Seltz in the commune of Tandel, Luxembourg, and what is its population?", scope="wikipedia", response_format=PLACE_SUMMARY_FORMAT, ) ``` ## Beyond Answer Chat completions accepts the same `response_format` and `scope` combination. See [Structured outputs](/chat-completions/guides/structured-outputs) if that's your integration surface instead of `answer` directly. Agent takes the same JSON-schema shape too, as `output_schema`, but runs its own open-ended multi-step research rather than a single grounding search scoped to one of these four verticals. See [Structured output](/agent/guides/structured-output) for when that's the better fit. ## Next steps * [Data Concepts](/data/data-concepts): How scopes work across Seltz endpoints * [Request a structured response](/answer/guides/structured-response): Full `response_format` parameter contract for Answer * [Structured outputs](/chat-completions/guides/structured-outputs): The same feature over chat completions * [Structured output](/agent/guides/structured-output): The same feature over the Agent API * [Companies](/data/companies), [People](/data/people), [News](/data/news), [Web](/data/web), [Wikipedia](/data/wikipedia): Full field list and sample response for each scope # Web Source: https://docs.seltz.ai/data/web Understanding the web scope The `web` scope includes pages from a general crawl of the web, together with Wikipedia articles. This scope is useful for general questions that do not fit a narrower scope: product information, documentation, how-tos, reference material, and topics covered across many kinds of site. > [!NOTE] > > **Beta.** The `web` scope is the default scope for Seltz endpoints. Its coverage and ranking can change while it is in beta. ## Includes This scope includes the text of crawled web pages, such as company and product sites, documentation, blogs, reference pages, and news sites, and the articles in the [`wikipedia`](/data/wikipedia) scope. ## Excludes This scope excludes non-written content, such as podcasts or videos. For results limited to news articles, use the [`news`](/data/news) scope. ## Structure Search results from the `web` scope are structured as a list of pages, with each page containing the following fields: * URL -- The URL of the page. * Content -- The text content of the page in Markdown. * Published date -- The date the page was published, when it is known. ## Freshness Newly crawled pages are added to the `web` scope several times a day. # Wikipedia Source: https://docs.seltz.ai/data/wikipedia Understanding the wikipedia scope The `wikipedia` scope includes data from Wikipedia articles, including the article content and URL. This scope is useful for questions about general knowledge and historical events. ## Includes This scope includes the URLs and contents of Wikipedia articles written in English. ## Excludes This scope excludes content in languages other than English, non-article content such as talk pages or user pages, and content that is not part of the main article. ## Sample response Search results with the `wikipedia` scope in JSON format have the following structure: ```json theme={null} { "documents": [ { "url": "https://en.wikipedia.org/wiki/Seltz_%28Tandel%29", "content": "# Seltz (Tandel)\n\nSeltz (German pronunciation: [ˈzɛlts]; Luxembourgish: Selz or Sëlz) is a village in northeastern Luxembourg. It is situated in the commune of Tandel and has a population of 108 as of 2025. \"Geoportail\". map.geoportail.lu. Retrieved 2022-05-24. \"Registre national des personnes physiques RNPP : Population par localité\". data.public.lu. 2025-09-30. 49°52′48″N 6°11′21″E / 49.88000°N 6.18917°E / 49.88000; 6.18917 v t e" }, { "url": "https://en.wikipedia.org/wiki/Seltz", "content": "# Seltz\n\nSeltz (French pronunciation: [sɛlts]; German: Selz) is a commune in the Bas-Rhin department of the Grand Est region in north-eastern France. It is located on the Sauer River near its confluence with the Rhine, opposite the German town of Rastatt. The former Celtic settlement of Saliso near a crossing of the Rhine river was mentioned as the Roman castrum Saletio in the Notitia Dignitatum about 425. Later a part of the German stem duchy of Swabia, Emperor Otto I granted the area to his wife Adelaide of Burgundy in 968. Saint Adelaide established Selz Abbey in 991 and died here eight years later. In 1357 Emperor Charles IV of Luxembourg raised Selz to an Imperial city, after which the town joined the Alsatian Décapole league. It however lost its immediate status in 1414, when it was mediatised by Elector Palatine Louis III of Wittelsbach. Seltz finally was annexed by France in 1680. Église Saint-Étienne de Seltz was last built in 1954–6. Ferry Seltz - Plittersdorf (Germany). The Rhine ferry Seltz - Plittersdorf (in France: Bac Seltz - Plittersdorf or Bac de Seltz) is a reaction (cable) ferry across the Rhine south of Karlsruhe. It connects the French municipality of Seltz in Alsace on the left bank of the Rhine with the German village of Plittersdorf, a part of Rastatt, on the right bank of the Rhine. The operator is the French European Collectivity of Alsace, the use of the ferry is free. Seltz is twinned with: Obervellach, Austria Santa Adélia, Brazil Communes of the Bas-Rhin department \"Répertoire national des élus: les maires\". data.gouv.fr, Plateforme ouverte des données publiques françaises (in French). 2 December 2020. \"Populations de référence 2023\" (in French). National Institute of Statistics and Economic Studies. 18 December 2025. INSEE commune file Population municipale entre 1968 et 2023, INSEE \"Les bacs rhénans\". alsace.eu (in French and German). 11 March 2022. Retrieved 2022-11-07. Commune website Wikimedia Commons has media related to Seltz. v t e" } ] } ``` # Concepts Source: https://docs.seltz.ai/fetch/concepts Understanding requests, results and formats ## Overview **Fetch** turns URLs into LLM-ready content. You send up to 20 URLs in one call, and you get back one result per URL. Two things make up the model: * A **request** is a list of URLs plus what to do with them: which representations to return, which tier to bill, and how long one URL may take. * A **result** is the outcome for one URL — the content if the page could be fetched, an error code if it could not, and what was observed on the way (where redirects landed, what the origin's status code was). ## A failed page is not a failed call The call succeeds or fails on the **request**, not on the pages. A malformed body, an unusable parameter, a missing API key or an exhausted balance is a rejection: nothing is fetched and nothing is billed. Once the request is accepted, every URL gets a result. A page that could not be fetched arrives inside the same `200` with `status = "error"` and an `error.code`. That way an origin's own 404 stays distinguishable from the endpoint not existing. See [Handle errors](/fetch/guides/handle-errors). ## Results and correlation Results come back in the order the URLs were requested, but **`requested_url` is the key** — it is echoed back byte for byte, never normalized and never replaced by the post-redirect URL. Join on it rather than on position. That is also why two identical URLs in one request are rejected: a repeated key is ambiguous. Where redirects actually landed is `final_url`. ## Formats `formats` selects which representations to return, by name. Leaving it out returns Markdown. Each requested format arrives in its own field on the result, so a format the page could not produce is simply unset — branch on `status`, not on whether a content field is present. ## Next steps To learn more about Seltz Fetch, check out the following topics: * [Quickstart](/fetch/quickstart) — Fetching your first page * [Handle errors](/fetch/guides/handle-errors) — Telling a rejected request from a failed page * [Reference](/fetch/reference) — Endpoint, SDK methods, limits and errors # Handle errors Source: https://docs.seltz.ai/fetch/guides/handle-errors Telling a rejected request from a failed page Fetch fails in two places, and they need different handling. A **rejected request** raises. Nothing was fetched and nothing was billed. A **failed page** does not raise: the call returns `200`, and that URL's result carries `status = "error"` with an `error.code`. So a caller has to do both — catch around the call, and branch inside the loop. ```python Python theme={null} from seltz import ( FetchStatus, Seltz, SeltzAPIError, SeltzAuthenticationError, SeltzConnectionError, SeltzRateLimitError, SeltzTimeoutError, ) client = Seltz() try: response = client.fetch(["https://example.com/"]) except SeltzAuthenticationError: print("Error: Invalid API key") except SeltzConnectionError: print("Error: Could not connect to Seltz API") except SeltzTimeoutError: print("Error: Request timed out") except SeltzRateLimitError: print("Error: Rate limit exceeded, try again later") except SeltzAPIError as e: print(f"API Error: {e.grpc_code} - {e.grpc_details}") else: for result in response.results: if result.status == FetchStatus.FETCH_STATUS_OK: print(result.requested_url, len(result.markdown)) else: print(result.requested_url, result.error.code, result.error.message) ``` ```typescript TypeScript theme={null} import { FetchStatus, Seltz, SeltzAPIError, SeltzAuthenticationError, SeltzConnectionError, SeltzRateLimitError, SeltzTimeoutError, } from "seltz"; const client = new Seltz(); try { const response = await client.fetch({ urls: ["https://example.com/"] }); for (const result of response.results) { if (result.status === FetchStatus.OK) { console.log(result.requestedUrl, result.markdown?.length); } else { console.log(result.requestedUrl, result.error?.code, result.error?.message); } } } catch (error) { if (error instanceof SeltzAuthenticationError) { console.error("Error: Invalid API key"); } else if (error instanceof SeltzConnectionError) { console.error("Error: Could not connect to Seltz API"); } else if (error instanceof SeltzTimeoutError) { console.error("Error: Request timed out"); } else if (error instanceof SeltzRateLimitError) { console.error("Error: Rate limit exceeded, try again later"); } else if (error instanceof SeltzAPIError) { console.error(`API error: ${error.code} - ${error.message}`); } else { throw error; } } ``` > [!NOTE] > > Branch on `status`, not on whether `markdown` is set. A format the page could > not produce is unset on an otherwise successful result. Treat an unrecognized `error.code` as a generic failure rather than rejecting the result — the set grows. The `message` beside it is prose for logs, and its wording can change; never parse it. The per-URL codes and the request-level codes are listed in [Reference](/fetch/reference#errors). # Quickstart Source: https://docs.seltz.ai/fetch/quickstart Getting started with Seltz Fetch Fetch turns a URL into LLM-ready Markdown. Send up to 20 in one call; they are fetched concurrently, so a batch takes about as long as its slowest page. ```python Python theme={null} from seltz import FetchStatus, Seltz client = Seltz(api_key="your-api-key") response = client.fetch(["https://example.com/"]) for result in response.results: if result.status == FetchStatus.FETCH_STATUS_OK: print(result.markdown) ``` ```typescript TypeScript theme={null} import { FetchStatus, Seltz } from "seltz"; const client = new Seltz({ apiKey: "your-api-key" }); const response = await client.fetch({ urls: ["https://example.com/"] }); for (const result of response.results) { if (result.status === FetchStatus.OK) { console.log(result.markdown); } } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/fetch \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"urls": ["https://example.com/"]}' ``` That is a complete request. `tier` selects the price and resolves to `"pro"` when omitted, so there is nothing else to send. The response holds one result per requested URL, in the order requested. A page that could not be fetched is still in there, with `status = "error"` — see [Handle errors](/fetch/guides/handle-errors). Give each URL a shorter budget with `timeout_ms`. It applies **per URL**, not to the batch. ```python Python theme={null} response = client.fetch( ["https://example.com/", "https://example.org/"], timeout_ms=15000, ) for result in response.results: print(result.requested_url, result.final_url, result.http_status_code) ``` ```typescript TypeScript theme={null} const response = await client.fetch({ urls: ["https://example.com/", "https://example.org/"], timeoutMs: 15000, }); for (const result of response.results) { console.log(result.requestedUrl, result.finalUrl, result.httpStatusCode); } ``` ```bash cURL theme={null} curl -X POST https://api.seltz.ai/v1/fetch \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "urls": ["https://example.com/", "https://example.org/"], "timeout_ms": 15000 }' ``` ## Next steps To learn more about Seltz Fetch, check out the following topics: * [Concepts](/fetch/concepts) — Requests, results and formats * [Handle errors](/fetch/guides/handle-errors) — Telling a rejected request from a failed page * [Reference](/fetch/reference) — Endpoint, SDK methods, limits and errors # Reference Source: https://docs.seltz.ai/fetch/reference Reference information for the Fetch API ## Endpoint | Method | Path | Operation | | - | - | - | | `POST` | `/v1/fetch` | Fetch up to 20 URLs | Over REST, authenticate with the `x-api-key` header against `https://api.seltz.ai`. The SDKs speak gRPC to `grpc.seltz.ai`; pass your API key to the client constructor and it is sent on every call. ## SDK methods | Python | TypeScript | Returns | | - | - | - | | `fetch` | `fetch` | `results` — one per requested URL | Both return the response message whole rather than the list inside it. ## Request | Field | Type | Default | | - | - | - | | `urls` | list of strings | required | | `formats` | list of strings | `["markdown"]` | | `tier` | string | `"pro"` | | `timeout_ms` | integer | the service maximum | `formats` names the representations to return. `"markdown"` returns the page's main content with navigation, footers and ads removed. An unrecognized name is rejected rather than dropped. Sending an empty list is the same as leaving the field out. `tier` selects the price. `"pro"` is the only value, and an omitted `tier` resolves to it. An unrecognized value is rejected rather than defaulted. `timeout_ms` is a wall-clock budget for **one URL**, not for the batch. URLs are fetched concurrently, so a batch takes roughly as long as its slowest entry. A URL that runs out gets `error.code = "timeout"`; the others are unaffected. Values above the maximum are clamped, not rejected. ## Result | Field | Set when | | - | - | | `requested_url` | Always. Echoed byte for byte from the request. | | `status` | Always. `ok` or `error`. | | `error` | `status` is `error`. Carries `code` and `message`. | | `markdown` | The page produced Markdown and it was requested. | | `fetched_at` | The content was retrieved. RFC 3339. | | `final_url` | Always on a successful fetch. Equal to `requested_url` when nothing redirected. | | `http_status_code` | A network response was observed for the main document. | | `content_type` | The origin declared a media type. Parameters are stripped. | ## Limits | Limit | Value | | - | - | | URLs per request | 1 minimum, 20 maximum | | URL length | 2048 bytes | | Per-URL timeout | 75 seconds maximum, and the default | | Request body | 65536 bytes | Duplicate URLs in one request are rejected: `requested_url` is the correlation key, and a repeated key is ambiguous. ## Errors ### The request was rejected A rejection is a non-200 with this body. Nothing was fetched and nothing was billed. The `code` is a stable name and is the field to branch on; the `message` is prose and can change. ```json theme={null} { "error": { "code": "INVALID_REQUEST", "message": "urls accepts at most 20 entries, got 21" } } ``` | Status | Code | When | | - | - | - | | 400 | `INVALID_REQUEST` | A malformed body, an empty or over-long `urls` list, a duplicate or over-long URL, or an unavailable `formats` or `tier` value. | | 401 | `UNAUTHENTICATED` | Invalid or missing API key. | | 402 | `INSUFFICIENT_CREDITS` | The balance cannot cover the request. | | 429 | `RATE_LIMITED` | Too many requests. Retry later. | | 500 | `INTERNAL` | A fault on our side. | Unrecognized fields in the request body are ignored. > [!NOTE] > > Over gRPC the same failures arrive as status codes rather than as this body. ### One URL failed A page that could not be fetched is **not** a rejection. It arrives inside the `200` as that result's `status = "error"` and an `error.code`. | Code | Meaning | | - | - | | `invalid_url` | Not a parseable absolute URL. | | `unsupported_scheme` | Parseable, but not `http` or `https`. | | `url_not_accessible` | DNS, connection, TLS or navigation failure reaching the origin. | | `timeout` | The fetch did not finish within `timeout_ms`. | | `unsupported_content_type` | The document is not an HTML page. PDFs, images, archives and other binaries are out of scope. | | `extraction_failed` | The page was fetched, but no requested format could be produced from it. | | `upstream_error` | The fetch path itself failed. Ours, not the URL's. | The set grows, so treat an unrecognized code as a generic failure rather than rejecting the result. ## Enum spelling `status` is spelled three ways depending on where you read it. | Where | Spelling | | - | - | | REST / JSON | `"ok"`, `"error"` — lowercase | | TypeScript SDK | `FetchStatus.OK`, `FetchStatus.ERROR` | | Python SDK | `FetchStatus.FETCH_STATUS_OK`, `FetchStatus.FETCH_STATUS_ERROR` | # Source: https://docs.seltz.ai/index # Web Knowledge for AI Agents Fast, up-to-date web data, providing context-engineered web content with sources for real-time AI reasoning. [Get Started](/quickstart) [Try Playground](https://console.seltz.ai/playground) ```python Python theme={null} from seltz import Seltz client = Seltz() # Search the Web response = client.search("best ai search engines", max_results=10) # Access results for document in response.documents: print(f"URL: {document.url}") print(f"Content: {document.content}") # Or get a direct answer with citations answer = client.answer("Who is going to be Apple's next CEO?") print(answer.answer) ``` ```typescript TypeScript theme={null} import { Seltz } from "seltz"; const client = new Seltz(); // Search the Web const result = await client.search({ query: "best ai search engines", maxResults: 10 }); // Access results for (const doc of result.documents) { console.log(doc.url); console.log(doc.content); } // Or get a direct answer with citations const answer = await client.answer({ query: "Who is going to be Apple's next CEO?" }); console.log(answer.answer); ``` ```bash cURL theme={null} # Search the Web curl -X POST https://api.seltz.ai/v1/search \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "best ai search engines", "max_results": 10}' # Or get a direct answer with citations curl -X POST https://api.seltz.ai/v1/answer \ -H "x-api-key: $SELTZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "Who is going to be Apple'\''s next CEO?"}' ``` > **[Get Started](/quickstart)** > > Get your API key and make your first search in minutes. > **[Python SDK](/sdks/python/installation)** > > Official Python client with full type support. > **[Integrations](/integrations/mcp)** > > Connect with OpenAI, Anthropic, Agno, n8n, and more. ## Why Seltz? > **Built for AI** > > Returns structured documents optimized for LLM context windows. > **Real-time Results** > > Access current information, not stale training data. > **Simple API** > > Search for documents or get a direct, cited answer — one API call to add web knowledge to any agent. ## SDKs Official client libraries with type support and error handling. > **[Python](/sdks/python/installation)** > > pip install seltz > **[JavaScript](/sdks/javascript/installation)** > > npm install seltz > **[REST API](/sdks/rest/curl)** > > cURL and HTTP ## Integrations Add Seltz to your existing AI workflows. > **[OpenAI](/integrations/openai-tool-calling)** > > Use Seltz as a tool with GPT models. > **[Anthropic](/integrations/anthropic-tool-calling)** > > Use Seltz as a tool with Claude models. > **[Mistral](/integrations/mistral-tool-calling)** > > Use Seltz as a tool with Mistral models. > **[Agno](/integrations/agno)** > > Build research agents with SeltzTools. ## MCP Server Use Seltz with Claude Desktop, Cursor, Windsurf, and other MCP-compatible AI tools. > **[MCP Server](/integrations/mcp)** > > Native MCP integration for AI-powered IDEs and assistants. # Agno Source: https://docs.seltz.ai/integrations/agno Build AI agents with Seltz search capabilities using the Agno framework. [Agno](https://github.com/agno-agi/agno) is a lightweight framework for building AI agents. The `SeltzTools` toolkit enables your agents to perform semantic search queries as part of their reasoning and task execution. ## Installation Install the required packages: ```bash theme={null} pip install agno seltz ``` ## Quick Start Create an agent with Seltz search capabilities: ```python theme={null} from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.tools.seltz import SeltzTools agent = Agent( model=OpenAIChat(id="gpt-4o"), tools=[SeltzTools()], markdown=True, ) agent.print_response("Search for recent AI safety research") ``` ## Configuration ### Environment Variables Set your API key as an environment variable: ```bash theme={null} export SELTZ_API_KEY="your-api-key" ``` ### SeltzTools Parameters | Parameter | Type | Default | Description | | - | - | - | - | | `api_key` | `str` | `None` | API key. Falls back to `SELTZ_API_KEY` env var. | | `endpoint` | `str` | `grpc.seltz.ai` | gRPC API endpoint. | | `max_results` | `int` | `10` | Maximum documents to return per search. | | `show_results` | `bool` | `False` | Print search results to console. | | `insecure` | `bool` | `False` | Use insecure connection (dev only). | ```python theme={null} tools = SeltzTools( api_key="your-api-key", max_results=5, show_results=True, ) ``` ## Examples ### Basic Search Agent ```python theme={null} from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.tools.seltz import SeltzTools agent = Agent( model=OpenAIChat(id="gpt-4o"), tools=[SeltzTools(show_results=True)], markdown=True, ) agent.print_response("What are the latest developments in quantum computing?") ``` ### Research Agent Build an agent that performs multiple searches and synthesizes findings into a report: ```python theme={null} from pathlib import Path from textwrap import dedent from agno.agent import Agent from agno.models.groq import Groq from agno.tools.seltz import SeltzTools tmp = Path("./reports") tmp.mkdir(exist_ok=True) agent = Agent( model=Groq(id="llama-3.3-70b-versatile"), tools=[SeltzTools(max_results=10, show_results=True)], description="You are an advanced AI researcher writing a report on a topic.", instructions=[ "For the provided topic, run 3 different searches.", "Read the results carefully and prepare a report.", "Focus on facts and make sure to provide references.", ], expected_output=dedent("""\ An engaging, informative, and well-structured report in markdown format: ## Engaging Report Title ### Overview ### Section 1 ... more sections as necessary... ### Takeaways ### References """ ), markdown=True, add_datetime_to_context=True, save_response_to_file=str(tmp.joinpath("{message}.md")), ) agent.print_response("Recent advances in AI safety", stream=True) ``` ### Multi-Model Support Seltz tools work with any model supported by Agno: ```python OpenAI theme={null} from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.tools.seltz import SeltzTools agent = Agent( model=OpenAIChat(id="gpt-4o"), tools=[SeltzTools()], ) ``` ```python Anthropic theme={null} from agno.agent import Agent from agno.models.anthropic import Claude from agno.tools.seltz import SeltzTools agent = Agent( model=Claude(id="claude-sonnet-5"), tools=[SeltzTools()], ) ``` ```python Groq theme={null} from agno.agent import Agent from agno.models.groq import Groq from agno.tools.seltz import SeltzTools agent = Agent( model=Groq(id="llama-3.3-70b-versatile"), tools=[SeltzTools()], ) ``` ## Error Handling The toolkit handles Seltz-specific errors gracefully, returning informative messages to the agent: * **Configuration errors** - Missing or invalid API key * **Authentication errors** - Invalid credentials * **Connection errors** - Network issues * **Timeout errors** - Request exceeded time limit * **Rate limit errors** - Too many requests Agents can use these error messages to decide on retry strategies or alternative actions. ## Resources > **[Agno Documentation](https://docs.agno.com)** > > Full Agno framework documentation. > **[Agno v2.4.5 Release](https://github.com/agno-agi/agno/releases/tag/v2.4.5)** > > View the release notes # Anthropic Tool Calling Source: https://docs.seltz.ai/integrations/anthropic-tool-calling Integrate Seltz search as a tool for Claude models using the Anthropic API. This guide shows how to integrate Seltz as a search tool for Claude models. When Claude needs to search for information, it will call your Seltz-powered function and use the results to generate informed responses. ## Prerequisites * Anthropic API key ([Get one here](https://console.anthropic.com/)) * Seltz API key ([Get one here](https://console.seltz.ai/)) ## Installation ```bash theme={null} pip install anthropic seltz ``` ## Setup Set your API keys as environment variables: ```bash theme={null} export ANTHROPIC_API_KEY="your-anthropic-key" export SELTZ_API_KEY="your-seltz-key" ``` Initialize both clients: ```python theme={null} from anthropic import Anthropic from seltz import Seltz anthropic_client = Anthropic() seltz_client = Seltz() ``` ## Define the Search Tool Create a tool schema that describes the Seltz search function to Claude: ```python theme={null} tools = [ { "name": "search", "description": "Search for information on the web. Use this tool when you need current information or facts you don't know. Returns relevant documents with URLs and content.", "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "The search query" }, "max_results": { "type": "integer", "description": "Maximum number of results to return (default: 5)" } }, "required": ["query"] } } ] ``` ## Using the Messages API Here's a complete example using Anthropic's Messages API with Seltz search: ```python theme={null} import json from anthropic import Anthropic from seltz import Seltz # Initialize clients anthropic_client = Anthropic() seltz_client = Seltz() # Define the search tool tools = [ { "name": "search", "description": "Search for information on the web. Use this tool when you need current information or facts you don't know. Returns relevant documents with URLs and content.", "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "The search query" }, "max_results": { "type": "integer", "description": "Maximum number of results to return (default: 5)" } }, "required": ["query"] } } ] def search(query: str, max_results: int = 5) -> str: """Execute a Seltz search and return results as JSON.""" response = seltz_client.search(query, max_results=max_results) results = [ {"url": doc.url, "content": doc.content} for doc in response.documents ] return json.dumps(results) def chat(user_message: str) -> str: """Send a message and handle tool calls.""" messages = [{"role": "user", "content": user_message}] # Initial request response = anthropic_client.messages.create( model="claude-sonnet-5", max_tokens=4096, messages=messages, tools=tools ) # Check if Claude wants to use a tool if response.stop_reason == "tool_use": # Claude may ask for several tools in one turn, and every tool_use # block needs its own tool_result in the next message. tool_results = [] for block in response.content: if block.type != "tool_use" or block.name != "search": continue result = search( query=block.input["query"], max_results=block.input.get("max_results", 5) ) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result }) # Send the tool results back to Claude messages = [ {"role": "user", "content": user_message}, {"role": "assistant", "content": response.content}, {"role": "user", "content": tool_results} ] # Get final response final_response = anthropic_client.messages.create( model="claude-sonnet-5", max_tokens=4096, messages=messages, tools=tools ) return "".join( block.text for block in final_response.content if hasattr(block, "text") ) # No tool use, return the response directly return "".join( block.text for block in response.content if hasattr(block, "text") ) # Example usage if __name__ == "__main__": response = chat("What are the latest developments in quantum computing?") print(response) ``` ## How It Works 1. **User sends a message** - The user asks a question that may require search 2. **Claude decides to search** - The model determines if it needs external information 3. **Tool use is returned** - Claude returns a `tool_use` block with the search query 4. **Seltz executes search** - Your code calls Seltz with the query 5. **Results sent back** - Search results are sent as a `tool_result` 6. **Final response** - Claude generates a response using the search results ``` User Question → Claude → Tool Use → Seltz Search → Tool Result → Claude → Final Answer ``` ## Streaming Responses Stream the final response for better user experience: ```python theme={null} def chat_stream(user_message: str): """Send a message with streaming response.""" messages = [{"role": "user", "content": user_message}] # Initial request (non-streaming to check for tool use) response = anthropic_client.messages.create( model="claude-sonnet-5", max_tokens=4096, messages=messages, tools=tools ) if response.stop_reason == "tool_use": # Every tool_use block needs its own tool_result in the next message, # and Claude may ask for several in one turn. tool_results = [] for block in response.content: if block.type != "tool_use" or block.name != "search": continue result = search( query=block.input["query"], max_results=block.input.get("max_results", 5) ) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result }) messages = [ {"role": "user", "content": user_message}, {"role": "assistant", "content": response.content}, {"role": "user", "content": tool_results} ] # Stream the final response with anthropic_client.messages.stream( model="claude-sonnet-5", max_tokens=4096, messages=messages, tools=tools ) as stream: for text in stream.text_stream: print(text, end="", flush=True) print() else: for block in response.content: if hasattr(block, "text"): print(block.text) ``` ## Multi-Turn Conversations Maintain conversation history for follow-up questions: ```python theme={null} import json from anthropic import Anthropic from seltz import Seltz class SearchAssistant: def __init__(self): self.anthropic = Anthropic() self.seltz = Seltz() self.messages = [] self.tools = [ { "name": "search", "description": "Search the web.", "input_schema": { "type": "object", "properties": { "query": {"type": "string"}, "max_results": {"type": "integer"}, }, "required": ["query"], }, } ] def search(self, query: str, max_results: int = 5) -> str: response = self.seltz.search(query, max_results=max_results) results = [ {"url": doc.url, "content": doc.content} for doc in response.documents ] return json.dumps(results) def chat(self, user_message: str) -> str: self.messages.append({"role": "user", "content": user_message}) response = self.anthropic.messages.create( model="claude-sonnet-5", max_tokens=4096, messages=self.messages, tools=self.tools ) if response.stop_reason == "tool_use": # Add assistant response with tool use self.messages.append({"role": "assistant", "content": response.content}) # Every tool_use block needs its own tool_result in the next # message, and Claude may ask for several in one turn. tool_results = [] for block in response.content: if block.type != "tool_use" or block.name != "search": continue result = self.search( query=block.input["query"], max_results=block.input.get("max_results", 5) ) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result }) # Add the tool results self.messages.append({"role": "user", "content": tool_results}) # Get final response final_response = self.anthropic.messages.create( model="claude-sonnet-5", max_tokens=4096, messages=self.messages, tools=self.tools ) content = "".join( block.text for block in final_response.content if hasattr(block, "text") ) self.messages.append({"role": "assistant", "content": content}) return content # No tool use content = "".join( block.text for block in response.content if hasattr(block, "text") ) self.messages.append({"role": "assistant", "content": response.content}) return content # Usage assistant = SearchAssistant() print(assistant.chat("What is quantum computing?")) print(assistant.chat("Which companies are leading in this field?")) ``` ## Error Handling Handle Seltz errors gracefully so Claude can respond appropriately: ```python theme={null} from seltz import ( Seltz, SeltzError, SeltzAuthenticationError, SeltzRateLimitError, SeltzTimeoutError, ) def search(query: str, max_results: int = 5) -> str: """Execute search with error handling.""" try: response = seltz_client.search(query, max_results=max_results) results = [ {"url": doc.url, "content": doc.content} for doc in response.documents ] return json.dumps(results) except SeltzAuthenticationError: return json.dumps({"error": "Search authentication failed"}) except SeltzRateLimitError: return json.dumps({"error": "Search rate limit exceeded, try again later"}) except SeltzTimeoutError: return json.dumps({"error": "Search timed out"}) except SeltzError as e: return json.dumps({"error": f"Search failed: {str(e)}"}) ``` ## Best Practices > **Write clear tool descriptions** > > Claude uses the description to decide when to use the tool. Be explicit about what information the search provides and when it should be used. > **Limit results appropriately** > > More results mean more tokens and higher costs. Use `max_results` to balance comprehensiveness with efficiency. > **Handle errors gracefully** > > Return error information as JSON so Claude can inform the user or suggest alternatives. > **Use streaming for better UX** > > Stream the final response so users see output immediately rather than waiting for the complete response. ## Next Steps > **[OpenAI Tool Calling](/integrations/openai-tool-calling)** > > Integrate with OpenAI models. > **[Agno Framework](/integrations/agno)** > > Build autonomous agents with Seltz. # Dify Source: https://docs.seltz.ai/integrations/dify Add web search capabilities to your Dify workflows with the Seltz plugin. [Dify](https://dify.ai) is an open-source LLM app development platform. The Seltz plugin enables your Dify agents and workflows to perform real-time web searches.