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

# Output Formatting

> Convert Virtual API responses to Markdown, CSV, or YAML — via spec config, URL extension, or Accept header content negotiation.

# Output Formatting

**JSON in, any format out. Zero client code.**

Virtual API responses are JSON by default. Output formatting lets you convert responses to Markdown, CSV, or YAML — configured in your Virtual API Configuration, requested via URL extension, or negotiated through the HTTP `Accept` header. Especially useful for AI agents that present data to humans, or for feeding governed data directly into spreadsheets and configuration tools.

<Info>
  Output formatting runs **after** [input normalization](../sources/input-normalization) and the ordered controls pipeline. It only changes how the final governed payload is rendered. See [Data Pipeline](../core/data-pipeline) for the full request flow.
</Info>

## Quick start

Add `default_output_format` to any Virtual API Configuration:

```yaml theme={null}
version: "0.3"
default_output_format: markdown
objects:
  customers:
    controls:
      - type: redact
        fields: [ssn]
      - type: tokenize
        fields: [email]
```

Every response from this Virtual API is now Markdown instead of JSON.

## Supported output formats

| Format     | Best for                                                   | Response `Content-Type`        |
| ---------- | ---------------------------------------------------------- | ------------------------------ |
| `json`     | System-to-system integrations and default runtime behavior | `application/json`             |
| `markdown` | AI agents, human-readable responses, quick inspection      | `text/markdown; charset=utf-8` |
| `csv`      | Spreadsheets, BI tools, exports                            | `text/csv; charset=utf-8`      |
| `yaml`     | Config workflows and human-readable structured output      | `text/yaml; charset=utf-8`     |

## Requesting a format

Use `default_output_format` to set a default, or request a format per call with a URL extension or `Accept` header.

<Tabs>
  <Tab title="Spec default">
    Set `default_output_format` in the Virtual API Configuration when one format should be returned by default.

    ```yaml theme={null}
    version: "0.3"
    default_output_format: markdown
    objects:
      customers:
        controls:
          - type: allow
            fields: [name, status]
    ```

    `default_output_format: json` is also valid when you want to make the contract explicit without changing behavior.
  </Tab>

  <Tab title="URL extension">
    Append an extension to any data endpoint URL to request a format without changing the Virtual API Configuration.

    ```bash theme={null}
    curl -H "dataharbor-api-key: YOUR_API_KEY" \
      https://service.dataharbor.co/fetch/b097a4d3/customers.md

    curl -H "dataharbor-api-key: YOUR_API_KEY" \
      https://service.dataharbor.co/fetch/b097a4d3/customers.csv

    curl -H "dataharbor-api-key: YOUR_API_KEY" \
      https://service.dataharbor.co/fetch/b097a4d3/customers.yaml
    ```

    URL extensions work on both `/fetch/` and `/relay/` endpoints.
  </Tab>

  <Tab title="Accept header">
    DataHarbor supports HTTP content negotiation via the `Accept` header.

    | Accept value       | Output format |
    | ------------------ | ------------- |
    | `application/json` | JSON          |
    | `text/markdown`    | Markdown      |
    | `text/csv`         | CSV           |
    | `text/yaml`        | YAML          |

    ```bash theme={null}
    curl -H "dataharbor-api-key: YOUR_API_KEY" \
      -H "Accept: text/markdown" \
      https://service.dataharbor.co/fetch/b097a4d3/customers
    ```

    Use this when the caller decides the best format at request time.
  </Tab>
</Tabs>

<Tip>
  `input_format` is a separate setting used during [input normalization](../sources/input-normalization). It affects how upstream data is parsed, not how the response is rendered.
</Tip>

## Precedence

Format selection follows a four-tier precedence (highest to lowest):

| Priority | Source          | Example                           |
| -------- | --------------- | --------------------------------- |
| 1        | URL extension   | `.md`, `.csv`, `.yaml`, `.json`   |
| 2        | `Accept` header | `Accept: text/markdown`           |
| 3        | Spec default    | `default_output_format: markdown` |
| 4        | Runtime default | JSON                              |

The first match wins.

| `default_output_format` | URL extension | Accept header   | Result                                      |
| ----------------------- | ------------- | --------------- | ------------------------------------------- |
| `markdown`              | `.json`       | `text/csv`      | **JSON** — URL extension wins               |
| `json`                  | *(none)*      | `text/markdown` | **Markdown** — Accept header wins over spec |
| `markdown`              | *(none)*      | *(none)*        | **Markdown** — spec default applies         |
| *(omitted)*             | *(none)*      | `text/csv`      | **CSV** — Accept header applies             |
| *(omitted)*             | `.md`         | `text/csv`      | **Markdown** — URL extension wins           |
| *(omitted)*             | *(none)*      | *(none)*        | **JSON** — default runtime behavior         |

## How each format renders

### Markdown

Markdown output is structure-driven.

| JSON shape            | Markdown representation      |
| --------------------- | ---------------------------- |
| Array of flat objects | Table                        |
| Single flat object    | Key-value list               |
| Nested object         | Headings plus nested content |
| Array of scalars      | Bullet list                  |
| Scalar value          | Plain text                   |

Example:

```json theme={null}
[
  { "name": "Alice", "role": "Admin" },
  { "name": "Bob", "role": "Viewer" }
]
```

```markdown theme={null}
| name | role |
|---|---|
| Alice | Admin |
| Bob | Viewer |
```

See [Markdown Input](./markdown) if your upstream source itself is Markdown.

### CSV

CSV output follows [RFC 4180](https://datatracker.ietf.org/doc/html/rfc4180).

| JSON shape               | CSV representation                     |
| ------------------------ | -------------------------------------- |
| Array of flat objects    | Header row plus one row per object     |
| Single flat object       | Header row plus a single row           |
| Nested objects or arrays | Serialized as JSON strings in the cell |
| Null values              | Empty cell                             |
| Scalar value             | Plain text                             |

Values containing commas, quotes, or newlines are automatically quoted. Objects with different key sets are handled by using the union of all keys in the header row.

### YAML

YAML output uses standard [YAML 1.2](https://yaml.org/spec/1.2.2/) formatting.

| JSON shape                      | YAML representation           |
| ------------------------------- | ----------------------------- |
| Object                          | `key: value` pairs            |
| Array of objects                | `- key: value` block sequence |
| Array of scalars                | `- item` list                 |
| Strings with special characters | Quoted with escape sequences  |
| Numbers and booleans            | Unquoted                      |
| Null                            | `null`                        |

## Response headers

The response `Content-Type` reflects the selected output format, and `Vary: Accept` is always included so caches differentiate negotiated responses correctly.

## Advanced HTTP negotiation

<AccordionGroup>
  <Accordion title="AI agents and Accept headers">
    Many AI agents already send `Accept` headers that prefer Markdown. For example, Claude Code typically sends `Accept: text/markdown, text/html, */*`, which means DataHarbor can return Markdown without any spec or URL changes.

    This behavior also carries through MCP delivery when the agent's `Accept` header is forwarded to the lease endpoint.
  </Accordion>

  <Accordion title="Quality values (q-values)">
    The `Accept` header supports quality values (`q`) to express preference.

    ```text theme={null}
    Accept: text/markdown, text/csv;q=0.5, application/json;q=0.2
    ```

    DataHarbor picks the supported format with the highest q-value.
  </Accordion>

  <Accordion title="406 responses and caching">
    When the `Accept` header explicitly excludes all supported formats and contains no wildcard fallback, DataHarbor returns `406 Not Acceptable`.

    When `Accept` is `*/*` or missing, DataHarbor falls through to the spec default or JSON behavior instead. Responses always include `Vary: Accept` for cache correctness.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Connect Sources" icon="database-zap" href="../sources">
    Start with how upstream formats enter DataHarbor
  </Card>

  <Card title="Input Normalization" icon="arrow-down-up" href="../sources/input-normalization">
    See how upstream JSON, CSV, YAML, and Markdown are parsed
  </Card>

  <Card title="Markdown Input" icon="file-text" href="./markdown">
    Learn how Markdown sources normalize before controls run
  </Card>

  <Card title="REST API Delivery" icon="globe" href="./rest-api">
    Request formatted responses over fetch and relay endpoints
  </Card>
</CardGroup>
