---
title: Pricing & Billing API
description: Free tier limits, pay-as-you-go pricing model, usage endpoints, and quota enforcement. Never billed for failures or cache hits.
slug: pricing-api
---

# Pricing & Billing

**Free tier:** 500 renders/month, no cost, never billed.
**Pay-as-you-go:** $0.003 per render above the free limit, no monthly minimum.

A render is **billable only when:**
- HTTP status is **200** (success)
- AND cache is **MISS** (not served from cache)
- AND tier is **payg** (upgrade from free)

**Failures (4xx/5xx) are never billed.** Cache hits are never billed. This is not an exception — it's the rule.

---

## Free Tier

You get **500 renders per calendar month** for free, with no credit card required. Usage is tracked automatically; when you exceed 500, the API returns `402 quota_exceeded` and you can upgrade or wait until next month.

Free tier renders:
- Are rate-limited at **60 requests/minute** per IP address
- Count against your quota only if they succeed (`status=200`) and are not cache hits
- Never incur a charge

**Upgrade at any time** in your [dashboard](https://renderog.app/dashboard).

---

## Pay-As-You-Go (PAYG)

After 500 renders per month, every additional render costs **$0.003**.

**Monthly cost example:**
- 500 renders: $0 (free tier)
- 1,000 renders: $0 + (500 × $0.003) = $1.50
- 10,000 renders: $0 + (9,500 × $0.003) = $28.50
- 50,000 renders: $0 + (49,500 × $0.003) = $148.50

**Billing happens automatically:**
1. Each render is logged with `status`, `cache_hit`, and your tier
2. Daily at 02:00 UTC, billable renders (200 + cache-miss + payg) are aggregated
3. A usage record is sent to your Lemon Squeezy subscription
4. You're billed at the end of your billing period (usually monthly)

There is no setup fee, no monthly minimum, and no charge for failures or cache hits.

---

## Quota & Rate Limiting

### Free Tier Rate Limit

**60 requests per minute** per IP address (for public `/og` endpoints) or per API key (for `/v1/*`).

If exceeded, the API returns `429 rate_limited`:
```json
{
  "error": { "code": "rate_limited", "message": "60 requests per minute exceeded" },
  "headers": { "Retry-After": "30" }
}
```

Wait the number of seconds in `Retry-After`, then retry.

### PAYG Tier Rate Limit

**600 requests per minute** per API key. Same `Retry-After` behavior.

### Free Tier Quota

**500 successful, non-cached renders per calendar month**. Once exceeded, the API returns `402 quota_exceeded`:

```json
{
  "error": { "code": "quota_exceeded", "message": "Free tier quota of 500 renders/month exceeded" }
}
```

Upgrade to PAYG in your dashboard, or wait until the 1st of next month for the quota to reset.

---

## Usage Endpoint

Check your current month's usage with your API key.

### `GET /v1/usage`

**Request:**
```bash
curl -H "Authorization: Bearer sk_live_..." https://api.renderog.app/v1/usage
```

**Response (200 OK):**
```json
{
  "month": "2026-07",
  "renders": 1240,
  "quota": 500,
  "tier": "payg",
  "billed_renders": 740
}
```

| Field | Type | Notes |
|---|---|---|
| `month` | string | YYYY-MM format, current calendar month |
| `renders` | int | Total successful, non-cached renders this month |
| `quota` | int | Your free quota (usually 500, 0 if no free tier) |
| `tier` | string | `free`, `payg`, or `flat` |
| `billed_renders` | int | Renders above quota that incurred a charge |

**Calculation:**
- `renders` = count of (status 200 + cache miss) this month
- `billed_renders` = max(0, renders - quota) if tier is `payg`
- Cost this month = `billed_renders × $0.003`

---

## API Key Management

Create, rotate, and revoke API keys from your dashboard or via the `POST /v1/keys` endpoint.

### `POST /v1/keys`

**Rotate your key** (create a new one, keep the old one active for 24 hours):

```bash
curl -X POST \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "action": "rotate" }' \
  https://api.renderog.app/v1/keys
```

**Response (200 OK):**
```json
{
  "key": "sk_live_a1b2c3d4e5f6...",
  "key_id": "550e8400-e29b-41d4-a716-446655440000",
  "created_at": "2026-07-22T10:00:00Z",
  "message": "New key created. Old key remains active for 24 hours. Store the new key securely."
}
```

The full key is shown **exactly once**. Store it immediately; you won't see it again. Only the hash is stored server-side.

**Revoke a key** (immediately stop accepting it):

```bash
curl -X POST \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "action": "revoke", "key_id": "550e8400-e29b-41d4-a716-446655440000" }' \
  https://api.renderog.app/v1/keys
```

**Response (200 OK):**
```json
{
  "message": "Key revoked"
}
```

Revoked keys are immediately invalid. Requests with revoked keys return `401 invalid_key`.

---

## Billing Examples

### Example 1: Free Tier (Under Quota)

**Scenario:** Free tier account, 350 renders in July.

| Field | Value |
|---|---|
| `renders` | 350 |
| `quota` | 500 |
| `tier` | free |
| `billed_renders` | 0 |
| **Cost** | **$0** |

### Example 2: Free Tier (Over Quota)

**Scenario:** Free tier account, 600 renders in July.

Requesting `GET /v1/usage` at 550 renders returns `402 quota_exceeded`. After upgrade to PAYG:

| Field | Value |
|---|---|
| `renders` | 600 |
| `quota` | 500 |
| `tier` | payg |
| `billed_renders` | 100 |
| **Cost** | **$0.30** (100 × $0.003) |

### Example 3: PAYG Tier (High Volume)

**Scenario:** PAYG account, 25,000 renders in July.

| Field | Value |
|---|---|
| `renders` | 25,000 |
| `quota` | 500 |
| `tier` | payg |
| `billed_renders` | 24,500 |
| **Cost** | **$73.50** (24,500 × $0.003) |

### Example 4: Cache Hits Not Billed

**Scenario:** PAYG account, 1,000 total requests in July.
- 600 unique renders (cache miss) → **billed**
- 400 requests for same URLs (cache hit) → **not billed**

| Field | Value |
|---|---|
| `renders` | 600 (cache misses only) |
| `quota` | 500 |
| `tier` | payg |
| `billed_renders` | 100 |
| **Cost** | **$0.30** |

Cache hits are served from Cloudflare's edge in under 5ms and cost nothing.

### Example 5: Failures Not Billed

**Scenario:** PAYG account, 100 requests total.
- 80 successful (200) → **billed** (if cache miss and over quota)
- 15 bad params (400) → **not billed**
- 5 rate limit (429) → **not billed**

Only the 80 successful renders count toward quota and billing.

---

## Best Practices

1. **Use caching aggressively.** The same OG image is cached for 1 year; cache hits cost nothing.
2. **Batch requests if possible.** Avoid hammering the same URL multiple times in rapid succession; wait for the first response to cache.
3. **Check usage regularly.** Call `GET /v1/usage` once a day or weekly to track consumption.
4. **Monitor rate limits.** Respect the `Retry-After` header; implement exponential backoff for transient failures.
5. **Rotate keys periodically.** Use `POST /v1/keys` to rotate credentials every few months.
6. **Keep your SIGNING_SECRET secret.** Never expose it in client-side code; always sign URLs on the server.

---

## Billing Transparency

**Never bill failed renders or cache hits.** This is a core promise and cannot change without explicit notice and opt-in. Here's how it's enforced:

- Every request is logged with `status`, `cache_hit`, and the user's tier
- A SQL function (`log_and_check_quota`) atomically computes `billed = (status=200 AND NOT cache_hit AND tier='payg')`
- Only rows with `billed=true` are sent to Lemon Squeezy as usage records
- You can audit your usage logs in your dashboard

---

## Questions?

- **Email:** support@renderog.app
- **Status:** [status.renderog.app](https://status.renderog.app)
- **GitHub issues:** [fgandolfo/renderog/issues](https://github.com/fgandolfo/renderog/issues)

See [Error Codes](./errors.md) for 402, 401, and other status codes. See [Quickstart](./quickstart.md) for signing examples.
