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

# Response headers, caching, and CORS

> How DexPaprika sets cache-control, what stale-if-error means for your client, current CORS limitations, and the response headers you can rely on.

## Why this matters

The DexPaprika REST API sits behind Cloudflare with caching enabled. The behavior is generally invisible if you are a server-side consumer making fresh requests, but a few headers change what your client should do when the origin is briefly unavailable, and the CORS story is incomplete enough to bite browser-side code.

This page documents the headers you will see today.

***

## Cache-Control

Every successful GET returns a `Cache-Control` header that looks like this:

```
cache-control: public, max-age=60, s-maxage=60, stale-while-revalidate=10, stale-if-error=600
```

Breaking down what each directive means for your client:

| Directive                   | Meaning                                                                                                                          |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `public`                    | The response may be cached by shared caches (CDN, proxies).                                                                      |
| `max-age=60`                | Your client may serve from its own cache for 60 seconds before revalidating.                                                     |
| `s-maxage=60`               | Shared caches may serve from cache for 60 seconds.                                                                               |
| `stale-while-revalidate=10` | After max-age expires, caches may serve stale for 10 more seconds while fetching a fresh copy in the background.                 |
| `stale-if-error=600`        | If revalidation fails, caches may keep serving the stale copy for up to 600 seconds (10 minutes) instead of failing the request. |

The practical effect of `stale-if-error=600` is that a brief origin outage does not break your client. If the API is unreachable, your CDN (or a properly configured HTTP cache library) returns the last-good response with `Age: <some_number>` indicating staleness. The data is not real-time during the outage, but your application keeps working.

**For agents and SDK consumers:** most HTTP client libraries do not honor `stale-if-error` by default. If your application needs the resilience this directive promises, use a cache layer that implements it (e.g., a CDN in front of your service, or libraries like `cachecontrol` for Python, `node-cache-control` for Node.js).

**For LLM tool calls:** the MCP layer does not currently cache between calls, so `stale-if-error` does not protect MCP-driven flows. The underlying REST cache still helps because Cloudflare serves the stale copy upstream of the MCP.

***

## Cf-Cache-Status

A Cloudflare-specific header that tells you whether your request hit the CDN cache:

```
cf-cache-status: HIT      ← served from CDN cache
cf-cache-status: MISS     ← served from origin
cf-cache-status: BYPASS   ← cache intentionally skipped
cf-cache-status: DYNAMIC  ← cache not applicable (e.g., 4xx, 5xx, OPTIONS)
```

This is informational. You do not need to act on it. It is useful when debugging stale data: if you see `HIT` and the data looks old, the cache is doing its job and the response is up to `max-age` seconds out of date.

***

## Age, Last-Modified

`Age` is the number of seconds since the cached response was generated. `Last-Modified` is the absolute timestamp of the underlying resource (when the API determined the snapshot).

```
age: 13
last-modified: Wed, 13 May 2026 13:18:11 GMT
```

If you need to tell your user how fresh the price data is, `Age` is the most direct signal: the data is at most `Age + max-age` seconds old.

***

## CORS

The DexPaprika REST API currently has **limited CORS support**. Server-side code works without any special handling. Browser-side code that calls the API directly will fail the preflight check.

What the API returns today:

```
# OPTIONS preflight:
HTTP/2 204
allow: OPTIONS, GET
vary: Origin, accept-encoding

# Simple GET with Origin header:
HTTP/2 200
cache-control: public, max-age=60, ...
vary: Origin, accept-encoding
```

What is **missing** for browser fetches to work:

* `Access-Control-Allow-Origin`
* `Access-Control-Allow-Methods`
* `Access-Control-Allow-Headers`

Without these, browsers reject the response even though the network call succeeds. This is a known limitation. Work-arounds while it is being addressed:

1. **Server-side proxy.** Run a small worker (Cloudflare Workers, Vercel Edge, Deno Deploy) that fetches from `api.dexpaprika.com` and re-serves with the right CORS headers to your frontend.
2. **DexPaprika MCP.** Connect through `mcp.dexpaprika.com` from an agent runtime that handles the CORS layer for you.
3. **Static export.** If your data refreshes infrequently, fetch at build time and serve as static JSON.

For real-time browser dashboards, options 1 and 2 are the practical paths.

***

## X-Api-Plan

Every response carries the plan the request was evaluated against:

```
x-api-plan: keyless
```

A keyless request returns `keyless`. A request authenticated with a key returns that key's plan: `free` for a free API key, `dev`, `pro` or `enterprise` for a paid one. The same value is returned in the body of `GET /usage`, alongside credit counts when the request is authenticated on `api-pro.dexpaprika.com`. Use it to confirm a key is actually being applied: if you set an `Authorization` header and still see `keyless`, the key is not reaching the billing gate. Called on `api-pro.dexpaprika.com` with no `Authorization` header at all, that host answers `403` with an HTML block page rather than JSON; see [upgrading to Pro](/api-pro/upgrading).

***

## X-Credits-\* and RateLimit-\*

Every response also reports where you stand against your allowances:

```
x-credits-limit:     30000
x-credits-remaining: 15316
x-credits-reset:     0
ratelimit-limit:     10
ratelimit-remaining: 7
ratelimit-reset:     27
```

`x-credits-limit` and `x-credits-remaining` are your credit allowance and what is left of it. `x-credits-reset` is `0` on keyless and free keys, which count over a rolling 30-day window with no reset moment; on Dev and Pro it holds the end of the billing period. The `ratelimit-*` trio follows the IETF draft (`ratelimit-reset` is seconds until the window rolls). For pacing, treat a `429` and its `Retry-After` as authoritative rather than the value of `ratelimit-limit` on a `200`. The allowances themselves are on [Rate limits](/knowledge-base/rate-limits).

***

## h-hap

You will see this header on every response:

```
h-hap: 01
```

It is an internal infrastructure marker. You can ignore it. Future versions of the API may remove it without notice.

***

## Headers not present (today)

Some headers you might expect from a mature API are not currently emitted:

* **`Deprecation` (RFC 9745)** and **`Sunset` (RFC 8594)**: not set. When DexPaprika deprecates an endpoint or field in the future, these will appear with the relevant dates. Until then, deprecation status is documented in the API reference and in the [changelog](/changelog/changelog), not in headers.
* **`ETag` / `If-None-Match`**: not set. Use `Last-Modified` if you need to track resource freshness.

***

## Quick reference

```
cache-control:    public, max-age=60, ..., stale-if-error=600
cf-cache-status:  HIT | MISS | BYPASS | DYNAMIC
x-api-plan:       keyless | free | dev | pro | enterprise
x-credits-*:      limit | remaining | reset
ratelimit-*:      limit | remaining | reset
age:              <seconds since cached>
last-modified:    <RFC 7231 date>
vary:             Origin, Accept-Encoding
```

All response headers are lowercase per HTTP/2 convention. If you parse them from a library that returns header names verbatim, accept both `Cache-Control` and `cache-control`. Some intermediaries normalize, others do not.

For error responses (4xx, 5xx), most cache-related headers are absent and `cf-cache-status: DYNAMIC` is set. The error envelopes themselves are documented in [Error handling](/knowledge-base/error-handling).
