CSV format

text/csv content negotiation on /pulse, /pulse/series, /coverage. Drop the pandas → to_csv step from the analyst pipeline.

Last updated: 2026-04-27

The data endpoints support text/csv alongside the default application/json, so cohort 1 institutional analysts can pipe API output directly into Excel without an intermediate pandas step.

Negotiation

Either method works:

bash
# 1. Accept header (canonical content negotiation)
curl -H "X-API-Key: $VEACON_API_KEY" \
     -H "Accept: text/csv" \
     "https://veacon.io/api/v1/real-estate/pulse?sigungu_code=11680" \
     -o gangnam.csv

# 2. ?format= query param (browser-friendly — no header support needed)
curl -H "X-API-Key: $VEACON_API_KEY" \
     "https://veacon.io/api/v1/real-estate/pulse?sigungu_code=11680&format=csv" \
     -o gangnam.csv

Precedence:

  1. ?format=csv or ?format=json — explicit override
  2. Accept: text/csv (without application/json) → CSV
  3. Default → JSON

?format=json overrides Accept: text/csv — the query param is the strongest signal of intent. Invalid ?format= values return 400 INVALID_PARAMS rather than silently falling back.

Supported endpoints

EndpointCSV
/api/v1/real-estate/pulseYes
/api/v1/real-estate/pulse/seriesYes
/api/v1/real-estate/coverageYes
/api/v1/real-estate/dimensionsNo (nested structure — use JSON)
/api/v1/markets/*No (deferred)

Response format

  • Encoding: UTF-8 with BOM (). Required for Excel to open Korean correctly without mojibake.
  • Format: RFC 4180. Fields containing comma / quote / newline are double-quoted; embedded quotes are doubled (""").
  • Line terminator: \r\n.
  • Nested fields (source_mix, source_means, confidence_factors, periods_with_data, data_sources) are JSON-encoded inside a single cell. Excel shows them as JSON text; pandas can expand with df['source_mix'].apply(json.loads).

Envelope as response headers

The CSV body is pure data. Key ADR-015 envelope fields ride alongside as X-Veacon-* response headers so you keep trust context:

HeaderFrom envelope
X-Veacon-Confidence_meta.confidence
X-Veacon-Coverage-Estimate_meta.coverage_estimate
X-Veacon-Disclosure-Url_meta.disclosure_url
X-Veacon-Count_meta.count
X-Veacon-Tier_meta.tier
X-Veacon-Granularity_meta.granularity (coverage only)
X-Request-IDrequest correlation ID
X-RateLimit-Limit / X-RateLimit-Remainingper-call rate limit

Read them with curl -D - or requests.get(...).headers.

Error responses stay JSON

4xx and 5xx responses always return JSON regardless of Accept / ?format=. The error envelope carries critical context that doesn't survive CSV flattening:

  • 404 on /pulse still includes the Layer 1 envelope per ADR-015 acceptance — coverage_note, disclosure_url, etc.
  • 403 TIER_RESTRICTED includes current_tier, required_tier, upgrade_url.
  • 429 includes retry_after_seconds.

Branch on status code first, parse body second:

python
import requests, csv, io

r = requests.get(
    "https://veacon.io/api/v1/real-estate/pulse",
    headers={"X-API-Key": os.environ["VEACON_API_KEY"], "Accept": "text/csv"},
    params={"sigungu_code": "11680", "property_type": "office"},
)

if r.status_code == 200:
    rows = list(csv.DictReader(io.StringIO(r.text)))
    # rows = [{"sigungu_code": "11680", "median_price": "4150000000", ...}]
    confidence = r.headers.get("X-Veacon-Confidence")
elif r.status_code == 404:
    body = r.json()  # JSON envelope still
    print(body["_meta"]["coverage_note"])
else:
    r.raise_for_status()

Discovery semantics

/coverage returns 200 + header-only CSV when zero cohorts match — discovery's "nothing available" answer is valid (distinct from /pulse which 404s with envelope).

bash
$ curl -H "X-API-Key: ..." -H "Accept: text/csv" \
       "https://veacon.io/api/v1/real-estate/coverage?sigungu_code=99999"
sigungu_code,sigungu,gu,dong,property_type,transaction_type,...

Excel-direct workflow

bash
# Pull this quarter's 강남 office sales straight to a file Excel can open
curl -H "X-API-Key: $VEACON_API_KEY" \
  "https://veacon.io/api/v1/real-estate/pulse?sigungu_code=11680&property_type=office&transaction_type=sale&period=2026-Q1&format=csv" \
  -o gangnam-2026-q1.csv

# Excel: File → Open → gangnam-2026-q1.csv
# Korean renders correctly thanks to the UTF-8 BOM.

For multi-period analysis use /pulse/series — the period column carries the per-row period so a single CSV holds all your quarters:

bash
curl -H "X-API-Key: $VEACON_API_KEY" \
  "https://veacon.io/api/v1/real-estate/pulse/series?sigungu_code=11680&property_type=office&from_period=2025-Q1&to_period=2026-Q4&format=csv" \
  -o gangnam-yoy.csv

SDK note

The Python + Node SDKs default to JSON for typed-response ergonomics. For CSV, use the underlying HTTP client directly:

python
# Python
import os, requests

r = requests.get(
    "https://veacon.io/api/v1/real-estate/pulse",
    headers={"X-API-Key": os.environ["VEACON_API_KEY"], "Accept": "text/csv"},
    params={"sigungu_code": "11680"},
)
r.raise_for_status()
with open("out.csv", "wb") as f:
    f.write(r.content)  # bytes preserve the BOM
ts
// Node
const res = await fetch(
  "https://veacon.io/api/v1/real-estate/pulse?sigungu_code=11680",
  { headers: { "X-API-Key": process.env.VEACON_API_KEY!, Accept: "text/csv" } },
);
const csv = await res.text();
fs.writeFileSync("out.csv", csv);

A first-class CSV helper on the SDK lands in a follow-up release once cohort 1 traffic confirms the demand shape.