# Live URL & CDN API

Public CDN endpoints for image serving and live URL generation. For authenticated API, see [image-generation.md](image-generation.md).

---

## URL Architecture

All images are accessible via CDN at `https://cdn.banatie.app`:

| URL Pattern | Description | Routing |
|-------------|-------------|---------|
| `/{org}/{proj}/img/{uuid}` | Direct image by UUID | Caddy → MinIO (public) |
| `/{org}/{proj}/img/@{alias}` | Image by alias | Caddy → API → MinIO |
| `/{org}/{proj}/live/{scope}?prompt=...` | Live generation | Caddy → API → MinIO |

**Key points:**
- **UUID URLs** are permanent and directly cacheable (Cloudflare edge cache)
- **Alias URLs** resolve through API (allows dynamic reassignment)
- **Live URLs** generate on-demand with prompt-based caching

**Example URLs:**
```
https://cdn.banatie.app/default/my-project/img/7c4ccf47-41ce-4718-afbc-8c553b2c631a
https://cdn.banatie.app/default/my-project/img/@hero-banner
https://cdn.banatie.app/default/my-project/live/hero?prompt=mountain+sunset
```

---

## CDN Image Serving

```
GET /cdn/:orgSlug/:projectSlug/img/:imageIdOrAlias
```

**Authentication:** None - Public endpoint

Serve images by UUID (imageId) or project-scoped alias.

**Path Parameters:**

| Parameter | Description |
|-----------|-------------|
| `orgSlug` | Organization identifier |
| `projectSlug` | Project identifier |
| `imageIdOrAlias` | Image UUID or `@alias` |

**Examples:**

```
# By UUID (direct MinIO access, fastest)
GET /cdn/acme/website/img/7c4ccf47-41ce-4718-afbc-8c553b2c631a

# By alias (API resolution, then MinIO)
GET /cdn/acme/website/img/@hero
```

> **Note:** UUID requests are served directly from MinIO (Cloudflare cached). Alias requests require API resolution.

**Response:** Raw image bytes (not JSON)

**Response Headers:**

| Header | Value |
|--------|-------|
| `Content-Type` | `image/jpeg`, `image/png`, etc. |
| `Content-Length` | File size in bytes |
| `Cache-Control` | `public, max-age=31536000` (1 year) |
| `X-Image-Id` | Image UUID |

---

## Live URL Generation

```
GET /cdn/:orgSlug/:projectSlug/live/:scope
```

**Authentication:** None - Public endpoint

Generate images on-demand via URL parameters with automatic caching.

**Path Parameters:**

| Parameter | Description |
|-----------|-------------|
| `orgSlug` | Organization identifier |
| `projectSlug` | Project identifier |
| `scope` | Scope identifier (alphanumeric, hyphens, underscores) |

**Query Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `prompt` | string | Yes | - | Image description |
| `aspectRatio` | string | No | `"1:1"` | Aspect ratio |
| `autoEnhance` | boolean | No | `true` | Enable prompt enhancement |
| `template` | string | No | `"general"` | Enhancement template |

**Example:**

```
GET /cdn/acme/website/live/hero-section?prompt=mountain+landscape&aspectRatio=16:9
```

**Response:** Raw image bytes

### Cache Behavior

**Cache HIT** - Image exists in cache:
- Returns instantly
- No rate limit check
- Headers include `X-Cache-Status: HIT`

**Cache MISS** - New generation:
- Generates image using AI
- Stores in cache
- Counts toward rate limit
- Headers include `X-Cache-Status: MISS`

**Cache Key:** Computed from `projectId + scope + prompt + aspectRatio + autoEnhance + template`

### Response Headers

**Cache HIT:**

| Header | Value |
|--------|-------|
| `Content-Type` | `image/jpeg` |
| `Cache-Control` | `public, max-age=31536000` |
| `X-Cache-Status` | `HIT` |
| `X-Scope` | Scope identifier |
| `X-Image-Id` | Image UUID |

**Cache MISS:**

| Header | Value |
|--------|-------|
| `Content-Type` | `image/jpeg` |
| `Cache-Control` | `public, max-age=31536000` |
| `X-Cache-Status` | `MISS` |
| `X-Scope` | Scope identifier |
| `X-Generation-Id` | Generation UUID |
| `X-Image-Id` | Image UUID |
| `X-RateLimit-Limit` | `10` |
| `X-RateLimit-Remaining` | Remaining requests |
| `X-RateLimit-Reset` | Seconds until reset |

---

## IP Rate Limiting

Live URLs are rate limited by IP address:

| Limit | Value |
|-------|-------|
| New generations | 10 per hour per IP |
| Cache hits | Unlimited |

**Note:** Only cache MISS (new generations) count toward the limit. Cache HIT requests are not limited.

Rate limit headers are included on MISS responses:
- `X-RateLimit-Limit`: Maximum requests (10)
- `X-RateLimit-Remaining`: Remaining requests
- `X-RateLimit-Reset`: Seconds until reset

---

## Scope Management

Scopes organize live URL generation budgets. All scope endpoints require Project Key authentication.

### Create Scope

```
POST /api/v1/live/scopes
```

**Request Body:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `slug` | string | Yes | - | Unique identifier |
| `allowNewGenerations` | boolean | No | `true` | Allow new generations |
| `newGenerationsLimit` | number | No | `30` | Max generations in scope |
| `meta` | object | No | `{}` | Custom metadata |

**Example:**

```json
{
  "slug": "hero-section",
  "allowNewGenerations": true,
  "newGenerationsLimit": 50,
  "meta": { "description": "Hero section images" }
}
```

**Response:** `201 Created`

```json
{
  "success": true,
  "data": {
    "id": "scope-123",
    "projectId": "project-456",
    "slug": "hero-section",
    "allowNewGenerations": true,
    "newGenerationsLimit": 50,
    "currentGenerations": 0,
    "lastGeneratedAt": null,
    "meta": { "description": "Hero section images" },
    "createdAt": "2025-11-28T10:00:00.000Z"
  }
}
```

### Lazy Scope Creation

Scopes are auto-created on first live URL request if `project.allowNewLiveScopes = true`:

```
GET /cdn/acme/website/live/new-scope?prompt=...
// Creates "new-scope" with default settings
```

### List Scopes

```
GET /api/v1/live/scopes
```

**Query Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `slug` | string | - | Filter by exact slug |
| `limit` | number | `20` | Results per page (max: 100) |
| `offset` | number | `0` | Pagination offset |

**Response:**

```json
{
  "success": true,
  "data": [
    {
      "id": "scope-123",
      "slug": "hero-section",
      "allowNewGenerations": true,
      "newGenerationsLimit": 50,
      "currentGenerations": 12,
      "lastGeneratedAt": "2025-11-28T09:30:00.000Z"
    }
  ],
  "pagination": { "limit": 20, "offset": 0, "total": 3, "hasMore": false }
}
```

### Get Scope

```
GET /api/v1/live/scopes/:slug
```

Returns scope with statistics (currentGenerations, lastGeneratedAt).

### Update Scope

```
PUT /api/v1/live/scopes/:slug
```

```json
{
  "allowNewGenerations": false,
  "newGenerationsLimit": 100,
  "meta": { "description": "Updated" }
}
```

Changes take effect immediately for new requests.

### Regenerate Scope Images

```
POST /api/v1/live/scopes/:slug/regenerate
```

**Request Body:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `imageId` | string | Specific image UUID (optional) |

**Behavior:**
- If `imageId` provided: Regenerate only that image
- If `imageId` omitted: Regenerate all images in scope

Images are regenerated with exact same parameters. IDs and URLs are preserved.

### Delete Scope

```
DELETE /api/v1/live/scopes/:slug
```

**Cascade Behavior:**
- Scope record is **hard deleted**
- All images in scope are **hard deleted** (with MinIO cleanup)
- Follows alias protection rules (aliased images may be kept)

> **Warning:** This permanently deletes all cached images in the scope.

---

## Scope Settings

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `slug` | string | - | Unique identifier within project |
| `allowNewGenerations` | boolean | `true` | Whether new generations are allowed |
| `newGenerationsLimit` | number | `30` | Maximum generations in scope |

When `allowNewGenerations: false`:
- Cache HITs still work
- New prompts return 403 error

When `newGenerationsLimit` reached:
- Cache HITs still work
- New prompts return 429 error

---

## Authenticated Live Endpoint

```
GET /api/v1/live?prompt=...
```

**Authentication:** Project Key required

Alternative to CDN endpoint with prompt caching by hash.

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `prompt` | string | Yes | Image description |

**Cache Behavior:**
- Cache key: SHA-256 hash of prompt
- Cache stored in `prompt_url_cache` table
- Tracks hit count and last access

**Response Headers:**
- `X-Cache-Status`: `HIT` or `MISS`
- `X-Cache-Hit-Count`: Number of cache hits (on HIT)

---

## Error Codes

| HTTP Status | Code | Description |
|-------------|------|-------------|
| 400 | `SCOPE_INVALID_FORMAT` | Invalid scope slug format |
| 403 | `SCOPE_CREATION_DISABLED` | New scope creation not allowed |
| 404 | `ORG_NOT_FOUND` | Organization not found |
| 404 | `PROJECT_NOT_FOUND` | Project not found |
| 404 | `SCOPE_NOT_FOUND` | Scope does not exist |
| 409 | `SCOPE_ALREADY_EXISTS` | Scope slug already in use |
| 429 | `IP_RATE_LIMIT_EXCEEDED` | IP rate limit (10/hour) exceeded |
| 429 | `SCOPE_GENERATION_LIMIT_EXCEEDED` | Scope limit reached |

---

## Use Cases

### Dynamic Hero Images

```html
<img src="/cdn/acme/website/live/hero?prompt=professional+office+workspace&aspectRatio=16:9" />
```

First load generates, subsequent loads are cached.

### Product Placeholders

```html
<img src="/cdn/acme/store/live/products?prompt=product+placeholder+gray+box&aspectRatio=1:1" />
```

### Blog Post Images

```html
<img src="/cdn/acme/blog/live/posts?prompt=abstract+technology+background&aspectRatio=16:9&template=illustration" />
```

---

## See Also

- [Basic Generation](image-generation.md) - API-based generation
- [Advanced Generation](image-generation-advanced.md) - References, aliases, flows
- [Image Upload](images-upload.md) - Upload and manage images
