CSV format
text/csv content negotiation on /pulse, /pulse/series, /coverage. Drop the pandas → to_csv step from the analyst pipeline.
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:
# 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:
?format=csvor?format=json— explicit overrideAccept: text/csv(withoutapplication/json) → CSV- 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
| Endpoint | CSV |
|---|---|
/api/v1/real-estate/pulse | Yes |
/api/v1/real-estate/pulse/series | Yes |
/api/v1/real-estate/coverage | Yes |
/api/v1/real-estate/dimensions | No (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 withdf['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:
| Header | From 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-ID | request correlation ID |
X-RateLimit-Limit / X-RateLimit-Remaining | per-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
/pulsestill 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:
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).
$ 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
# 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:
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
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
// 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.