Sitelet https://roxyapi.com/docs/authentication
Skip to content
  1. Docs
  2. Getting Started
  3. Authentication

API key authentication

Every RoxyAPI request requires an API key. No OAuth, no tokens, no sessions.

Key types

Two key classes follow the Stripe convention. Pick the one that matches where the call is made from.

PrefixClassUse it fromBrowser safe
sk_live_..., sk_test_...SecretYour server, MCP, scripts, CLIsNo
pk_live_..., pk_test_...PublishableBrowser, widgets, no-code platforms, hosted embedsYes, with origin allowlist

pk_ keys can be safely placed in client-side JavaScript, HTML widgets, and no-code platform configs. Bind each pk_ key to one or more allowed origins (your website domains) and a leak from one of those origins becomes an empty exploit: the attacker hits your monthly quota and gets 403 origin_not_allowed from anywhere else. A native mobile app is not a browser: it sends no Origin header, so an allowlisted pk_ key is refused there and an unrestricted one can be lifted from the bundle and spent. Route mobile traffic through your own server with a secret key.

pk_ keys are NOT accepted on the MCP endpoints (/mcp/*). MCP is server-side traffic with no Origin header to enforce, so a leaked publishable key there would be free tool access. Use a secret key for MCP.

Getting your API key

Already a customer? Get your API key on the API Keys tab of your account, where you can also create another.

New here:

  1. Pick a plan on pricing and complete checkout
  2. Your key is created the moment payment clears. The welcome email carries a one-time link that shows it in full, once, and the link works for 30 minutes from the moment the key was created.

No sign-up form. No approval queue. Instant activation.

Where your key is shown, and for how long

A secret key (sk_) is shown in full exactly twice: behind the one-time link in your welcome email, and on the API Keys tab of your account for 30 minutes after it was created. After that the tab shows only its fingerprint, the prefix and the last four characters, such as sk_live_...4ddd. That fingerprint is not the key and will not authenticate a request. A secret key is never stored in a readable form, so nobody, including RoxyAPI, can show it again.

Missed the window or lost the key? Create a new one on the API Keys tab. It is shown once, right there, and your old key keeps working until you revoke it. A publishable key (pk_) stays readable on that tab for as long as it exists, because it ships inside your web pages anyway and is protected by its origin allowlist rather than by secrecy.

Using your API key

Headers are metadata you send along with your request. Think of the API key header like showing your ID at a door, where the server checks it before letting your request through.

Include the X-API-Key header in every request:

curl https://roxyapi.com/api/v2/astrology/horoscope/aries/daily \
  -H "X-API-Key: your_api_key_here"

New to fetch()? The Quickstart has an annotated example explaining every line.

Using a publishable key in the browser

A publishable key can be sent over Authorization: Bearer ... from any front-end. This avoids the extra preflight a custom header would force.

<script>
  fetch('https://roxyapi.com/api/v2/tarot/draw', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer pk_live_your_publishable_key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ count: 1 })
  })
    .then((r) => r.json())
    .then(console.log);
</script>

When the request runs from a browser, the browser sends an Origin header automatically. The server compares its host against the allowlist on your key (case-insensitive, protocol and port ignored, no wildcards), so you list plain domains like yourdomain.com and both http and https work. Mismatch returns 403 origin_not_allowed.

If you set no origins on a publishable key, every response includes the header X-Roxy-Warning: publishable_key_has_no_origin_restrictions. Add at least one origin before shipping to production.

Error responses

A key or origin problem answers 401 or 403 with one of these codes:

StatusCodeWhen
401api_key_requiredNo API key was sent.
401invalid_api_keyThe key sent is malformed or cannot be verified.
401api_key_revokedThe key was valid once and has since been revoked from the account page.
401subscription_not_foundThe key verifies, but the subscription behind it no longer exists.
401subscription_inactiveThe subscription behind the key is cancelled, expired or suspended, or a trial is past its end date.
401publishable_key_not_allowed_on_mcpA publishable key was used on a Remote MCP server, which takes a secret key.
401invalid_client_ipA keyless request could not be attributed to a caller.
401unauthorizedAuthentication failed with no more specific reason to give.
403forbiddenThe key is valid, but this request is not permitted.
403origin_requiredA publishable key with an origin allowlist was used with no Origin header, as from a server or a native app.
403origin_not_allowedThe calling site is not on the origin allowlist of this publishable key.

All errors return { "error": "message", "code": "machine_readable_code", "doc_url": "url" }. The error field is a plain-English description. The code field is stable and safe to switch on in your code (e.g., validation_error, api_key_required, rate_limit_exceeded). The doc_url field links straight to the entry for that code. Every code, with the fix for each, is on the error codes page.

Rate limits

Rate limit info is included in every response header:

  • X-RateLimit-Limit: your monthly request allowance
  • X-RateLimit-Remaining: requests left this month
  • X-RateLimit-Used: requests consumed this month
  • X-RateLimit-Reset: Unix timestamp (seconds, UTC) of the next quota reset

A 429 also carries Retry-After with the seconds until that reset, so an HTTP client that retries on 429 by default stops instead of repeating the call.

Every plan also has a per-minute limit that absorbs bursts. Going over it returns 429 with code rate_limit_per_minute and a Retry-After of at most 60 seconds, and does not count against your monthly allowance. The X-RateLimit-* headers always describe the monthly allowance.

Plans range from 50,000 to 3,000,000 requests/month, with custom enterprise volume above that. All endpoints count the same: one request, regardless of complexity.

Quotas reset on the 1st of every calendar month at 12:00 AM UTC. The window is the calendar month, not your billing period, so the reset never moves with your renewal date: a plan bought on the 20th still refills on the 1st, and an annual plan refills every month rather than once a year. Unused requests do not roll over. Over the limit, every call returns 429 with code rate_limit_exceeded until the rollover.

Security best practices

Never expose a SECRET key in client-side code. Anyone who views your page source can steal it, and a secret key carries no origin restriction to limit the damage. This is what NOT to do:

<!-- DANGER: Anyone can see this key by viewing page source -->
<script>
  fetch('https://roxyapi.com/api/v2/tarot/daily', {
    headers: { 'X-API-Key': 'sk_live_abc123...' }
  });
</script>

If you need to call the API from a browser, that is what a publishable pk_ key is for. See Using a publishable key in the browser above.

Instead, call RoxyAPI from your backend server and return the results to your frontend. The Templates show this pattern in practice.

Other best practices:

  • Use environment variables to store your key (ROXY_API_KEY), not hardcoded strings in your code.
  • Rotate your key if it is ever exposed. Contact support for a new key.

The quickstart example puts the key in browser code for learning purposes. That is fine for local testing, but never deploy it that way.

Next steps

  • SDK Setup: typed API calls in TypeScript and Python
  • MCP Setup: connect AI agents via Model Context Protocol