Every endpoint answers cross-origin requests from any origin, so browser JavaScript can call the API without a proxy in front of it.
const res = await fetch(
'https://api.apiverve.com/v1/currencyconverter?from=USD&to=EUR&amount=100',
{ headers: { 'x-api-key': KEY } }
);
const { data } = await res.json();Nothing has to be configured for that to work — that call hits currency converter and comes back like any other. The rest of this page is the two places it stops working.
What the API sends
Preflight OPTIONS requests are answered at the edge and never reach the API itself:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, HEAD
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000Max-Age is 20 days, so a browser sends the preflight once and then calls straight through for
as long as the tab lives.
The API answers with Access-Control-Allow-Origin: *. Browsers refuse to combine a wildcard
origin with a credentialed request, so adding credentials: 'include' turns a working call into
a CORS failure. Authentication is the x-api-key header — there is no cookie to send.
Reading response headers from JavaScript
By default fetch can only see a handful of response headers cross-origin. The API opts the
credit metadata in explicitly, so this works:
const res = await fetch(url, { headers: { 'x-api-key': KEY } });
console.log(res.headers.get('x-api-remaining-credits')); // "9814"
console.log(res.headers.get('x-api-max-credits')); // "100000"Exactly four headers are exposed, and this is the whole list:
| Header | Reads as |
|---|---|
x-api-remaining-credits | Credits left in the current cycle |
x-api-credits-used | Credits spent in the current cycle |
x-api-max-credits | The cycle's allowance |
x-api-version | Version of the endpoint that answered |
x-rate-limit-limit, x-rate-limit-remaining and x-rate-limit-reset are on the wire, but
they are not in the exposed set, so headers.get() returns null for them in browser
JavaScript. That is the browser hiding them, not the API omitting them — the same call from
a server reads them fine. If you need to pace a browser client, count your own calls against
the ceiling on rate limits rather than reading it back.
When a missing key looks like a network error
This is the one CORS problem worth knowing in advance.
A request with no x-api-key header is rejected at the edge, and that rejection is an HTML
403 that carries no CORS headers at all. The browser cannot read a response it was never
allowed to see, so it reports the only thing it can:
TypeError: Failed to fetch
No status code, no body, nothing in the console but a network failure — for what is really a
missing header. A wrong key behaves completely differently: it reaches the API, comes back as a
normal 401 with CORS headers and a JSON body, and your error handling works as written.
Check that the header is actually being sent before you look anywhere else. An undefined
environment variable produces x-api-key: undefined, which is a wrong key and gives a clean
401; a conditional that skipped the header entirely gives the opaque failure above. Confirm it
in the Network tab's request headers, then reproduce with curl to see the real status.
Putting a key in frontend code
A key in browser JavaScript is a key you have published. Anyone can read it from the bundle or the Network tab. That is sometimes fine and sometimes not:
- Fine — a prototype, an internal tool, or a low-cost endpoint behind a key you are willing to rotate.
- Not fine — your production key. Use a sub-key for the frontend so revoking it costs you nothing, and keep the account's primary key server-side. Key scoping can pin that sub-key to the one endpoint the page calls, which bounds what a lifted key is worth.
For anything user-facing, the safer shape is a thin endpoint of your own that holds the key and forwards the call. You get to rate-limit per user, cache repeated lookups, and rotate the key without shipping a new bundle. See security.
Next
Header-by-header detail is in request headers. Status codes and retry behaviour are in errors.
If the reason you are calling from the browser is a form or a lookup widget, embedded forms do it without a key on the page at all, and mock endpoints let a front end develop against the shape before the real call exists.