Skip to main content

CSV & Caching (ETag/304)

We serve CSV with strong ETag so you can use conditional requests:

GET /v1/ports/USLAX/trend?format=csv
ETag: "abcd1234"

GET (conditional) If-None-Match: "abcd1234" -> 304 Not Modified

Also expect Cache-Control: public, max-age=300 on read endpoints.


id: csv-etag title: CSV & Caching (ETag/304) sidebar_label: CSV & ETag

PortPulse serves CSV responses with a strong ETag. Pair it with If-None-Match to avoid re-downloading unchanged data and to get HTTP 304 Not Modified when the content is identical.

TL;DR
  1. First GET returns 200 OK with an ETag header.
  2. Cache the body + ETag locally.
  3. Next time, send If-None-Match: <etag> → if unchanged, server returns 304 (no body).
    Also expect Cache-Control: public, max-age=300 on read endpoints.

Endpoints that support CSV + ETag

Most read endpoints accept a format=csv query parameter and return ETag-enabled CSV:

  • /v1/ports/{UNLOCODE}/trend?format=csv
  • /v1/ports/{UNLOCODE}/snapshot?format=csv
  • /v1/ports/{UNLOCODE}/dwell?format=csv
  • /v1/hs/{code}/imports?format=csv (beta)

Strong validator: PortPulse ETags are strong validators for CSV (not weak W/ prefixes).

Basic flow (HTTP)

# 1) First request
GET /v1/ports/USLAX/trend?days=30&format=csv HTTP/1.1
Host: api.useportpulse.com
X-API-Key: dev_demo_123

HTTP/1.1 200 OK
Content-Type: text/csv
Cache-Control: public, max-age=300
ETag: "4b0a5dd8c6b7c0a0"

# CSV body...
# 2) Conditional request with If-None-Match
GET /v1/ports/USLAX/trend?days=30&format=csv HTTP/1.1
Host: api.useportpulse.com
X-API-Key: dev_demo_123
If-None-Match: "4b0a5dd8c6b7c0a0"

HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=300
ETag: "4b0a5dd8c6b7c0a0"

# No body

Client examples

# First fetch
curl -sS -H "X-API-Key: $API_KEY" \
"https://api.useportpulse.com/v1/ports/USLAX/trend?days=30&format=csv" \
-D headers.txt -o trend.csv

# Extract ETag and perform a conditional GET
ETAG=$(grep -i '^ETag:' headers.txt | awk '{print $2}' | tr -d '\r')

curl -sS -H "X-API-Key: $API_KEY" \
-H "If-None-Match: $ETAG" \
"https://api.useportpulse.com/v1/ports/USLAX/trend?days=30&format=csv" \
-D headers2.txt -o trend_2.csv

# If unchanged → HTTP/1.1 304 and trend_2.csv will be empty.

Behavior & semantics

AspectValue
Cache headerCache-Control: public, max-age=300 on read endpoints
ValidatorStrong ETag for CSV bodies
Conditional requestsSend If-None-Match: <etag> to receive 304 if unchanged
MethodsGET fully supported; HEAD may be used to probe headers (optional)
Scope of ETagCalculated from the exact CSV bytes of the response
Change conditionsData update, parameter change (e.g., window, fields), or bugfix that alters output order/format
Tip for schedulers

When polling, keep your last ETag and use If-None-Match. Only parse CSV when you get 200. This reduces bandwidth and rate-limit usage.

Gotchas

  • ETag is per URL. Any change to query params (like days=730d, fields=...) yields a different ETag.
  • Some proxies normalize headers. Always read the ETag value from the actual response you received.
  • If you see a 412 Precondition Failed, double-check the header name/value quoting.
  • Clients must handle both 200 and 304 paths; do not assume one or the other.

FAQ

Is ETag stable across regions?
Yes—ETag represents the bytes of the CSV payload and is consistent across PoPs for the same response.

Why 300s (5 minutes) max-age?
It balances freshness and cache efficiency for most operational dashboards. Use conditional requests for tighter polling.

Do JSON endpoints also have ETag?
JSON endpoints may include cache validators, but the strong ETag guarantee primarily targets CSV downloads.

See also