We built DataHarbor so that one API becomes many. Until now, that meant many governed views — each with different privacy rules, access controls, and field shapes — but the wire format was still JSON.
That changes today. The same Virtual API can now respond in Markdown, CSV, YAML, or JSON — set in your spec, requested with a URL suffix, or negotiated automatically through the HTTP Accept header.
Why this matters
The world that consumes your APIs is no longer just frontends and mobile apps. Agents, spreadsheets, config pipelines, internal wikis, and LLM tool-call chains all want your data — and they do not all want JSON.
Think about who’s calling your Virtual API today:
- An AI agent asking for customer records to summarize — Markdown is often easier to work with than deeply nested JSON.
- A business analyst pulling usage data into Google Sheets — CSV is the lingua franca.
- A DevOps pipeline injecting config from your API into a Helm chart — YAML slots in natively.
- A partner integration that needs structured data — JSON is still the right answer.
Before today, you would build four endpoints, or force every consumer to transform the response themselves. Now, one Virtual API can handle all of them — and governance runs before any formatting happens.
Three ways to choose your format
There are three mechanisms to control which format your Virtual API responds with. They stack in a clear precedence order:
1. Set the default in your spec
Add default_output_format to your Virtual API Configuration, and every request that doesn’t specify otherwise will receive that format:
version: "0.3"
default_output_format: markdown
objects:
customers:
controls:
- type: redact
fields: [ssn]
- type: tokenize
fields: [email]
Once deployed, every response from this Virtual API is Markdown instead of JSON. No client-side changes needed.
2. Override per-request with a URL suffix
For one-off overrides, append .md, .csv, .yaml, or .json to the same endpoint:
# Default JSON endpoint
curl -H "dataHarbor-api-key: YOUR_API_KEY" \
https://service.dataharbor.co/fetch/b097a4d3/customers
# Same data, same governance — now as Markdown
curl -H "dataHarbor-api-key: YOUR_API_KEY" \
https://service.dataharbor.co/fetch/b097a4d3/customers.md
# Same data, same governance — now as CSV
curl -H "dataHarbor-api-key: YOUR_API_KEY" \
https://service.dataharbor.co/fetch/b097a4d3/customers.csv
# Same data, same governance — now as YAML
curl -H "dataHarbor-api-key: YOUR_API_KEY" \
https://service.dataharbor.co/fetch/b097a4d3/customers.yaml
This works on both /fetch/ and /relay/ endpoints. The suffix takes priority over both the Accept header and the spec default.
3. Let clients negotiate with Accept headers
DataHarbor respects standard HTTP content negotiation. If your client sends an Accept header, we honor it:
# Agent requests Markdown
curl -H "dataHarbor-api-key: YOUR_API_KEY" \
-H "Accept: text/markdown" \
https://service.dataharbor.co/fetch/b097a4d3/customers
# Spreadsheet integration requests CSV
curl -H "dataHarbor-api-key: YOUR_API_KEY" \
-H "Accept: text/csv" \
https://service.dataharbor.co/fetch/b097a4d3/customers
# Config tooling requests YAML
curl -H "dataHarbor-api-key: YOUR_API_KEY" \
-H "Accept: text/yaml" \
https://service.dataharbor.co/fetch/b097a4d3/customers
The precedence is: URL suffix > Accept header > spec default > upstream format. DataHarbor also honors standard q values in Accept, and only returns 406 Not Acceptable if a client excludes every supported format without a wildcard fallback.
What the output actually looks like
Here is a governed customer response returned as JSON — then the same governed result as Markdown and CSV. This example uses a one-item collection because arrays of flat objects render cleanly as Markdown tables and CSV rows.
JSON:
[
{
"id": 2417,
"name": "Meridian Properties",
"email": "tok_8f3a2b",
"plan": "enterprise",
"usage_this_month": 142800,
"ssn": ""
}
]
Markdown (.md):
| id | name | email | plan | usage_this_month | ssn |
|------|---------------------|------------|------------|------------------|-----|
| 2417 | Meridian Properties | tok_8f3a2b | enterprise | 142800 | |
CSV (.csv):
id,name,email,plan,usage_this_month,ssn
2417,Meridian Properties,tok_8f3a2b,enterprise,142800,
Notice that in every format the email is still tokenized and the SSN field is still present but emptied by redact. The governance layer runs first — formatting is just the last mile.
Governance still runs first
This is the part we are most proud of. Formatting is applied after your privacy controls, transforms, and access rules. The pipeline looks like this:
- Request hits your Virtual API
- DataHarbor authenticates and authorizes
- Source API is called (or cache is served)
- Control blocks execute — redaction, tokenization, and transforms
- Format conversion — the governed result is serialized into the requested format
- Response is returned with the correct
Content-Typeheader andVary: Accept
This means every consumer receives the same governed result — just in the shape the consumer needs.
For agents in particular
This is where adaptive output gets really interesting. Many AI agents and agent frameworks already ask for Markdown or work better when they receive it. Tables, headers, and lists are often easier for a model to work with than deeply nested JSON.
With DataHarbor, you can make any Virtual API agent-ready in seconds:
# Your existing endpoint — agents just add .md
curl -H "dataHarbor-api-key: YOUR_API_KEY" \
https://service.dataharbor.co/fetch/b097a4d3/customers.md
No new endpoints. No agent-specific variant. The same governed data your REST consumers are already using — just formatted for how agents actually read.
If you want the same governed data flow to reach agent tooling, pair this with Deliver via MCP. Adaptive output gives you a cleaner response shape without cloning endpoints or adding one-off post-processing.
When to use each format
| Format | Best for | Content-Type |
|---|---|---|
| JSON | Programmatic consumers, partner integrations | application/json |
| Markdown | AI agents, documentation, human-readable views | text/markdown; charset=utf-8 |
| CSV | Spreadsheets, bulk exports, data pipelines | text/csv; charset=utf-8 |
| YAML | Config injection, IaC pipelines, Kubernetes | text/yaml; charset=utf-8 |
Try it now
If you already have a Virtual API deployed, this works today — no configuration changes required. Start with a suffix on your next request, then set default_output_format in your spec once you know which consumers need which shape:
# Try it with your existing Virtual API
curl -H "dataHarbor-api-key: YOUR_API_KEY" \
https://service.dataharbor.co/fetch/YOUR_VAPI_ID/YOUR_ENDPOINT.md
Need the governance layer first? Start with Data Control and Data Transform, then come back and choose the output your consumers actually want.
For the full reference — including spec defaults, media type mapping, response headers, and edge cases — check out the output formatting documentation.