# Authentication

Every Roxy API 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.

| Prefix | Class | Use it from | Browser safe |
|---|---|---|---|
| `sk_live_...`, `sk_test_...` | Secret | Your server, MCP, scripts, CLIs | No |
| `pk_live_...`, `pk_test_...` | Publishable | Browser, widgets, no-code platforms, hosted embeds | Yes, with origin allowlist |

`pk_` keys can be safely placed in client-side JavaScript, HTML widgets, mobile app bundles, 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.

`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

1. Go to [roxyapi.com/pricing](/pricing)
2. Pick a plan and complete checkout
3. Your API key is displayed immediately and emailed to you. Mint additional keys (secret or publishable) from your account page anytime.

No account required. No approval queue. Instant activation.

## 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 — the server checks it before letting your request through.

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


### curl

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

### JavaScript

```javascript
const response = await fetch('https://roxyapi.com/api/v2/astrology/horoscope/aries/daily', {
  headers: {
    'X-API-Key': 'your_api_key_here',
    'Content-Type': 'application/json'
  }
});
const data = await response.json();
```

**Tip: New to `fetch()`? The [Quickstart](/docs/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.

```html
<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

| Status | Meaning | What to do |
|--------|---------|------------|
| `401` | Missing or invalid API key | Check that your `X-API-Key` header or `Authorization: Bearer` value is present and the key is correct |
| `403` `origin_required` | Publishable key with allowlist, but no `Origin` header | Call from a browser, or switch to a secret key for server-side use |
| `403` `origin_not_allowed` | `Origin` not in the allowlist for this key | Add the origin in your account page or use a key bound to this site |
| `429` | Rate limit exceeded | Wait and retry, or upgrade your plan for more requests |
| `400` | Invalid request parameters | Check the request body matches the endpoint schema |

All errors return `{ "error": "message", "code": "machine_readable_code" }`. 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`). See the [SDK docs](/docs/sdk#error-codes) for the full error codes table.

## 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

Plans range from 25,000 to 3,000,000 requests/month. All endpoints count the same: one request, regardless of complexity.

## Security best practices

**Never expose your API key in client-side code.** Anyone who views your page source can steal your key. This is what NOT to do:

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

Instead, call Roxy from your backend server and return the results to your frontend. The [Starter Apps](/docs/starters) 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](/contact) for a new key.

**Warning: 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](/docs/sdk) — typed API calls in TypeScript and Python
- [MCP Setup](/docs/mcp) — connect AI agents via Model Context Protocol
