> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seltz.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 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) |

**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:

<CodeGroup>
  ```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,
    },
  };
  ```
</CodeGroup>

## 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:

<CodeGroup>
  ```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<string, unknown> | null;
  try {
    snapshot = JSON.parse(result.answer);
  } catch {
    snapshot = null; // truncated or otherwise malformed -- don't trust a partial parse
  }
  ```
</CodeGroup>

`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), [Wikipedia](/data/wikipedia): Full field list and sample response for each scope
