diff --git a/docs/api/image-generation-advanced.md b/docs/api/image-generation-advanced.md new file mode 100644 index 0000000..2b5d74e --- /dev/null +++ b/docs/api/image-generation-advanced.md @@ -0,0 +1,449 @@ +# Advanced Image Generation + +Advanced generation features: reference images, aliases, flows, and regeneration. For basic generation, see [image-generation.md](image-generation.md). + +All endpoints require Project Key authentication via `X-API-Key` header. + +--- + +## Reference Images + +Use existing images as style or content references for generation. + +### Using References + +Add `referenceImages` array to your generation request: + +```json +{ + "prompt": "A product photo with the logo in the corner", + "referenceImages": ["@brand-logo", "@product-style"] +} +``` + +References can be: +- **Project aliases**: `@logo`, `@brand-style` +- **Flow aliases**: `@hero` (with flowId context) +- **Technical aliases**: `@last`, `@first`, `@upload` +- **Image UUIDs**: `550e8400-e29b-41d4-a716-446655440000` + +### Auto-Detection from Prompt + +Aliases in the prompt are automatically detected and used as references: + +```json +{ + "prompt": "Create a banner using @brand-logo with blue background" +} +// @brand-logo is auto-detected and added to referenceImages +``` + +### Reference Limits + +| Constraint | Limit | +|------------|-------| +| Max references | 3 images | +| Max file size | 5MB per image | +| Supported formats | PNG, JPEG, WebP | + +### Response with References + +```json +{ + "data": { + "id": "550e8400-...", + "prompt": "Create a banner using @brand-logo", + "referencedImages": [ + { "imageId": "7c4ccf47-...", "alias": "@brand-logo" } + ], + "referenceImages": [ + { + "id": "7c4ccf47-...", + "storageUrl": "http://...", + "alias": "@brand-logo" + } + ] + } +} +``` + +--- + +## Alias Assignment + +Assign aliases to generated images for easy referencing. + +### Project-Scoped Alias + +Use `alias` parameter to assign a project-wide alias: + +```json +{ + "prompt": "A hero banner image", + "alias": "@hero-banner" +} +``` + +The output image will be accessible via `@hero-banner` anywhere in the project. + +### Flow-Scoped Alias + +Use `flowAlias` parameter to assign a flow-specific alias: + +```json +{ + "prompt": "A hero image variation", + "flowId": "550e8400-...", + "flowAlias": "@best" +} +``` + +The alias `@best` is only accessible within this flow's context. + +### Alias Format + +| Rule | Description | +|------|-------------| +| Prefix | Must start with `@` | +| Characters | Alphanumeric, underscore, hyphen | +| Pattern | `@[a-zA-Z0-9_-]+` | +| Max length | 50 characters | +| Examples | `@logo`, `@hero-bg`, `@image_1` | + +### Reserved Aliases + +These aliases are computed automatically and cannot be assigned: + +| Alias | Description | +|-------|-------------| +| `@last` | Most recently generated image in flow | +| `@first` | First generated image in flow | +| `@upload` | Most recently uploaded image in flow | + +### Override Behavior + +When assigning an alias that already exists: +- The **new image gets the alias** +- The **old image loses the alias** (alias set to null) +- The old image is **not deleted**, just unlinked + +--- + +## 3-Tier Alias Resolution + +Aliases are resolved in this order of precedence: + +### 1. Technical Aliases (Highest Priority) + +Computed on-the-fly, require flow context: + +``` +GET /api/v1/images/@last?flowId=550e8400-... +``` + +| Alias | Returns | +|-------|---------| +| `@last` | Last generated image in flow | +| `@first` | First generated image in flow | +| `@upload` | Last uploaded image in flow | + +### 2. Flow Aliases + +Stored in flow's `aliases` JSONB field: + +``` +GET /api/v1/images/@hero?flowId=550e8400-... +``` + +Different flows can have the same alias pointing to different images. + +### 3. Project Aliases (Lowest Priority) + +Stored in image's `alias` column: + +``` +GET /api/v1/images/@logo +``` + +Global across the project, unique per project. + +### Resolution Example + +``` +// Request with flowId +GET /api/v1/images/@hero?flowId=abc-123 + +// Resolution order: +// 1. Is "@hero" a technical alias? No +// 2. Does flow abc-123 have "@hero" in aliases? Check flows.aliases JSONB +// 3. Does any image have alias = "@hero"? Check images.alias column +``` + +--- + +## Flow Integration + +Flows organize related generations into chains. + +### Lazy Flow Creation + +When `flowId` is not provided, a pending flow ID is generated: + +```json +// Request +{ + "prompt": "A red car" + // No flowId +} + +// Response +{ + "data": { + "id": "gen-123", + "flowId": "flow-456" // Auto-generated, flow record not created yet + } +} +``` + +The flow record is created when: +- A second generation uses the same `flowId` +- A `flowAlias` is assigned to any generation in the flow + +### Eager Flow Creation + +When `flowAlias` is provided, the flow is created immediately: + +```json +{ + "prompt": "A hero banner", + "flowAlias": "@hero-flow" +} +``` + +### No Flow Association + +To explicitly create without flow association: + +```json +{ + "prompt": "A standalone image", + "flowId": null +} +``` + +### flowId Behavior Summary + +| Value | Behavior | +|-------|----------| +| `undefined` (not provided) | Auto-generate pendingFlowId, lazy creation | +| `null` (explicitly null) | No flow association | +| `"uuid-string"` | Use provided ID, create flow if doesn't exist | + +--- + +## Regeneration + +### Regenerate Generation + +Recreate an image using the exact same parameters: + +``` +POST /api/v1/generations/:id/regenerate +``` + +**Behavior:** +- Uses exact same prompt, aspect ratio, references +- **Preserves** output image ID and URL +- Works regardless of current status +- No request body needed + +**Response:** Same as original generation with new image + +### Update and Regenerate + +Use PUT to modify parameters with smart regeneration: + +``` +PUT /api/v1/generations/:id +``` + +```json +{ + "prompt": "A blue car instead", + "aspectRatio": "1:1" +} +``` + +**Smart Behavior:** + +| Changed Field | Triggers Regeneration | +|---------------|----------------------| +| `prompt` | Yes | +| `aspectRatio` | Yes | +| `flowId` | No (metadata only) | +| `meta` | No (metadata only) | + +### Flow Regenerate + +Regenerate the most recent generation in a flow: + +``` +POST /api/v1/flows/:id/regenerate +``` + +**Behavior:** +- Finds the most recent generation in flow +- Regenerates with exact same parameters +- Returns error if flow has no generations + +--- + +## Flow Management + +### List Flows + +``` +GET /api/v1/flows +``` + +**Query Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `limit` | number | `20` | Results per page (max: 100) | +| `offset` | number | `0` | Pagination offset | + +**Response:** + +```json +{ + "data": [ + { + "id": "flow-456", + "projectId": "project-123", + "aliases": { "@hero": "img-789", "@best": "img-abc" }, + "generationCount": 5, + "imageCount": 7, + "createdAt": "2025-11-28T10:00:00.000Z" + } + ], + "pagination": { "limit": 20, "offset": 0, "total": 3, "hasMore": false } +} +``` + +### Get Flow + +``` +GET /api/v1/flows/:id +``` + +Returns flow with computed counts and aliases. + +### List Flow Generations + +``` +GET /api/v1/flows/:id/generations +``` + +Returns all generations in the flow, ordered by creation date (newest first). + +### List Flow Images + +``` +GET /api/v1/flows/:id/images +``` + +Returns all images in the flow (generated and uploaded). + +### Update Flow Aliases + +``` +PUT /api/v1/flows/:id/aliases +``` + +```json +{ + "aliases": { + "@hero": "image-id-123", + "@best": "image-id-456" + } +} +``` + +**Behavior:** Merges with existing aliases (does not replace all). + +### Remove Flow Alias + +``` +DELETE /api/v1/flows/:id/aliases/:alias +``` + +Example: `DELETE /api/v1/flows/flow-456/aliases/@hero` + +### Delete Flow + +``` +DELETE /api/v1/flows/:id +``` + +**Cascade Behavior:** +- Flow record is **hard deleted** +- All generations in flow are **hard deleted** +- Images **without** project alias: **hard deleted** with MinIO cleanup +- Images **with** project alias: **kept**, but `flowId` set to null + +--- + +## Full Request Example + +```json +// POST /api/v1/generations +{ + "prompt": "A professional product photo using @brand-style and @product-template", + "aspectRatio": "1:1", + "autoEnhance": true, + "enhancementOptions": { "template": "product" }, + "flowId": "campaign-flow-123", + "alias": "@latest-product", + "flowAlias": "@hero", + "meta": { "campaign": "summer-2025" } +} +``` + +**What happens:** +1. `@brand-style` and `@product-template` resolved and used as references +2. Prompt enhanced using "product" template +3. Generation created in flow `campaign-flow-123` +4. Output image assigned project alias `@latest-product` +5. Output image assigned flow alias `@hero` in the flow +6. Custom metadata stored + +--- + +## Response Fields (Additional) + +| Field | Type | Description | +|-------|------|-------------| +| `flowId` | string | Associated flow UUID | +| `alias` | string | Project-scoped alias (on outputImage) | +| `referencedImages` | array | Resolved references: `[{ imageId, alias }]` | +| `referenceImages` | array | Full image details of references | + +--- + +## Error Codes + +| HTTP Status | Code | Description | +|-------------|------|-------------| +| 400 | `ALIAS_FORMAT_CHECK` | Alias must start with @ | +| 400 | `RESERVED_ALIAS` | Cannot use technical alias | +| 404 | `ALIAS_NOT_FOUND` | Referenced alias doesn't exist | +| 404 | `FLOW_NOT_FOUND` | Flow does not exist | + +--- + +## See Also + +- [Basic Generation](image-generation.md) - Simple generation +- [Image Upload](images-upload.md) - Upload with aliases +- [Live URLs](live-url.md) - CDN and live generation diff --git a/docs/api/image-generation.md b/docs/api/image-generation.md index 9f8060a..2c8eb60 100644 --- a/docs/api/image-generation.md +++ b/docs/api/image-generation.md @@ -1,805 +1,343 @@ -# Banatie API - Image Generation (v1) +# Image Generation API -All endpoints require Project Key authentication via `X-API-Key` header and are rate-limited to 100 requests/hour. +Basic image generation using AI. For advanced features like references, aliases, and flows, see [image-generation-advanced.md](image-generation-advanced.md). + +All endpoints require Project Key authentication via `X-API-Key` header. --- -## Generations +## Create Generation -### POST /api/v1/generations +``` +POST /api/v1/generations +``` -Create new image generation with optional reference images, aliases, and auto-enhancement. +Generate an AI image from a text prompt. -**Authentication:** Project Key required +**Request Body:** -**Parameters:** -- `prompt` - Text description for image (required) -- `referenceImages` - Array of image references (aliases or image IDs) -- `aspectRatio` - Image aspect ratio (e.g., "16:9", "1:1", "3:2", "9:16", default: "1:1") -- `flowId` - Associate generation with a flow (UUID) -- `alias` - Assign project-scoped alias to output image (@custom-name) -- `flowAlias` - Assign flow-scoped alias to output image (requires flowId) -- `autoEnhance` - Enable prompt enhancement (boolean, default: true) -- `enhancementOptions` - Enhancement configuration (object, optional) - - `template` - Enhancement template: "photorealistic", "illustration", "minimalist", "sticker", "product", "comic", "general" -- `meta` - Custom metadata (JSON object) +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `prompt` | string | Yes | - | Text description of the image to generate | +| `aspectRatio` | string | No | `"1:1"` | Image aspect ratio | +| `autoEnhance` | boolean | No | `true` | Enable AI prompt enhancement | +| `enhancementOptions` | object | No | - | Enhancement configuration | +| `enhancementOptions.template` | string | No | `"general"` | Enhancement template | +| `meta` | object | No | `{}` | Custom metadata | -**Purpose:** Generate AI images with reference support and automatic alias assignment +**Example Request:** -**Response:** 201 Created with generation details, status, and output image +```json +{ + "prompt": "a red sports car on a mountain road", + "aspectRatio": "16:9", + "autoEnhance": true, + "enhancementOptions": { + "template": "photorealistic" + } +} +``` + +**Response:** `201 Created` + +```json +{ + "success": true, + "data": { + "id": "550e8400-e29b-41d4-a716-446655440000", + "projectId": "57c7f7f4-47de-4d70-9ebd-3807a0b63746", + "prompt": "A photorealistic establishing shot of a sleek red sports car...", + "originalPrompt": "a red sports car on a mountain road", + "autoEnhance": true, + "aspectRatio": "16:9", + "status": "pending", + "outputImageId": null, + "processingTimeMs": null, + "createdAt": "2025-11-28T10:00:00.000Z", + "updatedAt": "2025-11-28T10:00:00.000Z" + } +} +``` --- -### GET /api/v1/generations +## Aspect Ratios -List generations with filtering and pagination. +Supported aspect ratios for image generation: -**Authentication:** Project Key required +| Aspect Ratio | Use Case | +|--------------|----------| +| `1:1` | Square images, social media posts, profile pictures | +| `16:9` | Landscape, hero banners, video thumbnails | +| `9:16` | Portrait, mobile screens, stories | +| `3:2` | Photography standard, print | +| `21:9` | Ultra-wide banners, cinematic | + +--- + +## Prompt Enhancement + +By default, prompts are automatically enhanced by AI to produce better results. + +### How It Works + +When `autoEnhance: true` (default): +- Your original prompt is preserved in `originalPrompt` +- AI enhances it with style details, lighting, composition +- The enhanced version is stored in `prompt` and used for generation + +When `autoEnhance: false`: +- Both `prompt` and `originalPrompt` contain your original text +- No AI enhancement is applied + +### Enhancement Templates + +Use `enhancementOptions.template` to guide the enhancement style: + +| Template | Description | Best For | +|----------|-------------|----------| +| `general` | Balanced enhancement (default) | Most use cases | +| `photorealistic` | Photography terms, lighting, camera details | Realistic photos | +| `illustration` | Art style, composition, color palette | Artwork, drawings | +| `minimalist` | Clean, simple, essential elements | Logos, icons | +| `sticker` | Bold outlines, limited colors, vector style | Stickers, emojis | +| `product` | Studio lighting, materials, lifestyle context | E-commerce | +| `comic` | Action lines, expressions, panel composition | Comics, manga | + +### Example: With Enhancement + +```json +// Request +{ + "prompt": "a cat", + "autoEnhance": true, + "enhancementOptions": { "template": "photorealistic" } +} + +// Response +{ + "prompt": "A photorealistic close-up portrait of a domestic cat with soft fur, captured with an 85mm lens at f/1.8, natural window lighting creating soft shadows, detailed whiskers and expressive eyes, shallow depth of field with creamy bokeh background", + "originalPrompt": "a cat", + "autoEnhance": true +} +``` + +### Example: Without Enhancement + +```json +// Request +{ + "prompt": "a cat sitting on a windowsill", + "autoEnhance": false +} + +// Response +{ + "prompt": "a cat sitting on a windowsill", + "originalPrompt": "a cat sitting on a windowsill", + "autoEnhance": false +} +``` + +--- + +## Generation Status + +Generations go through these status stages: + +| Status | Description | +|--------|-------------| +| `pending` | Generation created, waiting to start | +| `processing` | AI is generating the image | +| `success` | Image generated successfully | +| `failed` | Generation failed (see `errorMessage`) | + +Poll the generation endpoint to check status: + +``` +GET /api/v1/generations/:id +``` + +When `status: "success"`, the `outputImageId` field contains the generated image ID. + +--- + +## List Generations + +``` +GET /api/v1/generations +``` + +List all generations with optional filters. **Query Parameters:** -- `flowId` - Filter by flow (UUID) -- `status` - Filter by status (pending|processing|success|failed) -- `limit` - Results per page (default: 20, max: 100) -- `offset` - Pagination offset (default: 0) -- `includeDeleted` - Include soft-deleted records (boolean, default: false) -**Purpose:** Browse generation history with optional filters - ---- - -### GET /api/v1/generations/:id - -Get single generation with full details. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Generation UUID (path parameter) - -**Purpose:** View complete generation details including output image, reference images, flow association, and timestamps - ---- - -### PUT /api/v1/generations/:id - -Update generation parameters with automatic regeneration. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Generation UUID (path parameter) -- `prompt` - New prompt (triggers regeneration) -- `aspectRatio` - New aspect ratio (triggers regeneration) -- `flowId` - Change flow association (null to detach, no regeneration) -- `meta` - Update custom metadata (no regeneration) - -**Purpose:** Update generation settings with intelligent regeneration behavior - -**Notes:** -- Changing `prompt` or `aspectRatio` triggers automatic regeneration -- Changing `flowId` or `meta` updates metadata only (no regeneration) -- Regeneration replaces existing output image (preserves ID and URLs) - ---- - -### POST /api/v1/generations/:id/regenerate - -Regenerate existing generation with exact same parameters. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Generation UUID (path parameter) - -**Purpose:** Recreate image using original parameters (prompt, aspect ratio, references) - -**Notes:** -- Uses exact same parameters as original generation -- Works regardless of current status (success, failed, pending) -- Replaces existing output image (preserves ID and URLs) -- No parameter modifications allowed (use PUT for changes) -- Useful for refreshing stale images or recovering from failures - ---- - -### POST /api/v1/generations/:id/retry - -**DEPRECATED:** Use POST /api/v1/generations/:id/regenerate instead - -Legacy endpoint maintained for backward compatibility. Delegates to /regenerate endpoint. - ---- - -### DELETE /api/v1/generations/:id - -Delete generation and its output image. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Generation UUID (path parameter) - -**Purpose:** Remove generation record and associated output image - -**Notes:** -- Soft delete generation record (sets deletedAt timestamp) -- Hard delete output image if no project/flow aliases exist -- Soft delete output image if aliases exist (preserves for CDN access) -- Cascades to remove generation-image relationships - ---- - -## Flows - -Flows organize related generations into chains and support flow-scoped aliases. Flows are created automatically when generations or uploads specify a flowId. - -### GET /api/v1/flows - -List all flows with pagination. - -**Authentication:** Project Key required - -**Query Parameters:** -- `limit` - Results per page (default: 20, max: 100) -- `offset` - Pagination offset (default: 0) - -**Purpose:** Browse all flows with computed generation and image counts - -**Notes:** -- Flows are created automatically when: - - A generation/upload specifies a flowId - - A generation/upload provides a flowAlias (eager creation) - ---- - -### GET /api/v1/flows/:id - -Get single flow with details. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Flow UUID (path parameter) - -**Purpose:** View flow metadata, aliases, and computed counts (generationCount, imageCount) - ---- - -### GET /api/v1/flows/:id/generations - -List all generations in a flow. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Flow UUID (path parameter) - -**Query Parameters:** -- `limit` - Results per page (default: 20, max: 100) -- `offset` - Pagination offset (default: 0) - -**Purpose:** View all generations associated with this flow, ordered by creation date (newest first) - ---- - -### GET /api/v1/flows/:id/images - -List all images in a flow. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Flow UUID (path parameter) - -**Query Parameters:** -- `limit` - Results per page (default: 20, max: 100) -- `offset` - Pagination offset (default: 0) - -**Purpose:** View all images (generated and uploaded) associated with this flow, ordered by creation date (newest first) - ---- - -### PUT /api/v1/flows/:id/aliases - -Update flow aliases. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Flow UUID (path parameter) -- `aliases` - Object mapping alias names to image IDs (request body) - -**Purpose:** Add or update flow-scoped aliases for image referencing - -**Notes:** -- Merges with existing aliases (does not replace all) -- Aliases must map to valid image IDs -- Aliases are stored in JSONB field for efficient lookups - ---- - -### DELETE /api/v1/flows/:id/aliases/:alias - -Remove specific flow alias. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Flow UUID (path parameter) -- `alias` - Alias name to remove (path parameter, e.g., "@hero") - -**Purpose:** Delete a single alias from flow's alias map, other aliases remain unchanged - ---- - -### POST /api/v1/flows/:id/regenerate - -Regenerate the most recent generation in a flow. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Flow UUID (path parameter) - -**Purpose:** Regenerate the latest generation in the flow - -**Notes:** -- Identifies the most recent generation (ordered by creation date) -- Uses exact same parameters from original generation -- Returns error if flow has no generations -- Replaces existing output image (preserves ID and URLs) - ---- - -### DELETE /api/v1/flows/:id - -Delete flow. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Flow UUID (path parameter) - -**Purpose:** Remove flow (hard delete) - -**Notes:** -- Flow record is hard deleted (no soft delete) -- Generations remain intact (not cascaded) -- Images remain intact (not cascaded) -- Flow-scoped aliases are removed with flow -- Generations and images lose their flow association but remain accessible - ---- - -## Images - -Database-tracked image management with upload, listing, metadata updates, and deletion. - -### POST /api/v1/images/upload - -Upload image file with database tracking. - -**Authentication:** Project Key required - -**Form Parameters (multipart/form-data):** -- `file` - Image file (required, max 5MB, PNG/JPEG/WebP) -- `alias` - Project-scoped alias (optional, e.g., "@logo") -- `flowId` - Associate with flow (UUID, optional) -- `flowAlias` - Flow-scoped alias (optional, requires flowId, triggers eager creation) -- `meta` - Custom metadata (JSON string, optional) - -**Purpose:** Upload image with automatic database record creation and storage - -**FlowId Behavior:** -- `undefined` (not provided) → generates new UUID for automatic flow creation -- `null` (explicitly null) → no flow association -- `string` (specific value) → uses provided flow ID - -**Notes:** -- Creates both MinIO storage file and database entry -- Supports PNG, JPEG, JPG, WebP formats -- Maximum file size: 5MB -- Eager flow creation when flowAlias is provided - ---- - -### GET /api/v1/images - -List images with filtering and pagination. - -**Authentication:** Project Key required - -**Query Parameters:** -- `flowId` - Filter by flow (UUID) -- `source` - Filter by source (generated|uploaded) -- `alias` - Filter by project alias (exact match) -- `limit` - Results per page (default: 20, max: 100) -- `offset` - Pagination offset (default: 0) -- `includeDeleted` - Include soft-deleted records (boolean, default: false) - -**Purpose:** Browse image library with optional filters - ---- - -### GET /api/v1/images/resolve/:alias - -Resolve alias to image using 3-tier precedence. - -**Authentication:** Project Key required - -**Parameters:** -- `alias` - Alias to resolve (path parameter, e.g., "@hero", "@last") - -**Query Parameters:** -- `flowId` - Flow context for resolution (UUID, optional) - -**Purpose:** Lookup image by alias with technical → flow → project precedence - -**Resolution Order:** -1. Technical aliases (if matches @last, @first, @upload) - computed on-the-fly -2. Flow aliases (if flowId provided) - looked up in flow's JSONB aliases field -3. Project aliases (global) - looked up in images.alias column - -**Returns:** Image record with resolution metadata (imageId, scope, flowId, image details) - ---- - -### GET /api/v1/images/:id - -Get single image by ID. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Image UUID (path parameter) - -**Purpose:** View complete image metadata including storage URLs, project/flow associations, alias, source, file metadata, focal point, and custom metadata - ---- - -### PUT /api/v1/images/:id - -Update image metadata. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Image UUID (path parameter) -- `focalPoint` - Update focal point coordinates (object: {x: 0.0-1.0, y: 0.0-1.0}, optional) -- `meta` - Update custom metadata (JSON object, optional) - -**Purpose:** Modify non-generative image properties - -**Notes:** -- Alias assignment moved to separate endpoint PUT /images/:id/alias -- Focal point used for image cropping - ---- - -### PUT /api/v1/images/:id/alias - -Assign or update project-scoped alias. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Image UUID (path parameter) -- `alias` - Alias name (string, required in request body, e.g., "@hero-bg") - -**Purpose:** Set project-level alias for image referencing - -**Notes:** -- Alias must start with @ symbol -- Must be unique within the project -- Validates alias format and reserved names -- Checks for conflicts with existing aliases -- Replaces existing alias if image already has one - ---- - -### DELETE /api/v1/images/:id - -Delete image with storage cleanup. - -**Authentication:** Project Key required - -**Parameters:** -- `id` - Image UUID (path parameter) - -**Purpose:** Permanently remove image and its storage file - -**Notes:** -- Hard delete of image record (no soft delete) -- Removes file from MinIO storage permanently -- Cascades to delete generation-image relationships -- Removes image from flow aliases (if present) -- Cannot be undone - use with caution - ---- - -## CDN Endpoints - -Public endpoints for serving images and live URL generation without authentication. - -### GET /cdn/:orgSlug/:projectSlug/img/:filenameOrAlias - -Serve images by filename or project-scoped alias via public CDN. - -**Authentication:** None - Public endpoint - -**Parameters:** -- `orgSlug` - Organization slug (path parameter) -- `projectSlug` - Project slug (path parameter) -- `filenameOrAlias` - Filename or @alias (path parameter) - -**Purpose:** Public image serving for websites and applications - -**Response Format:** Raw image bytes (not JSON) - -**Response Headers:** -- `Content-Type` - image/jpeg (or appropriate MIME type) -- `Content-Length` - Actual byte length -- `Cache-Control` - public, max-age=31536000 (1 year) -- `X-Image-Id` - Image database UUID - -**Access Methods:** -- Filename: `GET /cdn/acme/website/img/hero-background.jpg` -- Alias: `GET /cdn/acme/website/img/@hero` - ---- - -### GET /cdn/:orgSlug/:projectSlug/live/:scope - -Live URL generation with automatic caching and scope management. - -**Authentication:** None - Public endpoint - -**Rate Limit:** 10 new generations per hour per IP (cache hits excluded) - -**Parameters:** -- `orgSlug` - Organization slug (path parameter) -- `projectSlug` - Project slug (path parameter) -- `scope` - Scope identifier (path parameter, alphanumeric + hyphens + underscores) - -**Query Parameters:** -- `prompt` - Image description (required) -- `aspectRatio` - Image aspect ratio (optional, default: "1:1") -- `autoEnhance` - Enable prompt enhancement (boolean, optional) -- `template` - Enhancement template (optional): photorealistic, illustration, minimalist, sticker, product, comic, general - -**Purpose:** On-demand image generation via URL parameters for dynamic content - -**Response Format:** Raw image bytes (not JSON) - -**Response Headers (Cache HIT):** -- `Content-Type` - image/jpeg -- `Content-Length` - Actual byte length -- `Cache-Control` - public, max-age=31536000 -- `X-Cache-Status` - HIT -- `X-Scope` - Scope identifier -- `X-Image-Id` - Image UUID - -**Response Headers (Cache MISS):** -- `Content-Type` - image/jpeg -- `Content-Length` - Actual byte length -- `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 - -**Caching Behavior:** -- **Cache HIT:** Returns existing image instantly, no rate limit check -- **Cache MISS:** Generates new image, counts toward IP rate limit -- Cache key computed from: prompt + aspectRatio + autoEnhance + template -- Cached images stored with meta.isLiveUrl = true - -**Scope Management:** -- Scopes separate generation budgets (e.g., "hero-section", "gallery") -- Auto-created on first use if allowNewLiveScopes = true -- Generation limits per scope (default: 30) -- Scope stats tracked (currentGenerations, lastGeneratedAt) - -**IP Rate Limiting:** -- 10 new generations per hour per IP address -- Separate from API key rate limits -- Cache hits do NOT count toward limit -- Only new generations (cache MISS) count -- Supports X-Forwarded-For header for proxy setups +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `status` | string | - | Filter by status: `pending`, `processing`, `success`, `failed` | +| `limit` | number | `20` | Results per page (max: 100) | +| `offset` | number | `0` | Pagination offset | +| `includeDeleted` | boolean | `false` | Include soft-deleted records | **Example:** + ``` -GET /cdn/acme/website/live/hero-section?prompt=mountain+landscape&aspectRatio=16:9 +GET /api/v1/generations?status=success&limit=10 +``` + +**Response:** + +```json +{ + "success": true, + "data": [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "prompt": "A photorealistic establishing shot...", + "originalPrompt": "a red sports car", + "autoEnhance": true, + "aspectRatio": "16:9", + "status": "success", + "outputImageId": "7c4ccf47-41ce-4718-afbc-8c553b2c631a", + "processingTimeMs": 8500, + "createdAt": "2025-11-28T10:00:00.000Z" + } + ], + "pagination": { + "limit": 10, + "offset": 0, + "total": 42, + "hasMore": true + } +} ``` --- -## Live Scopes Management +## Get Generation -Authenticated endpoints for managing live URL scopes. All require Project Key authentication. +``` +GET /api/v1/generations/:id +``` -### POST /api/v1/live/scopes +Get a single generation with full details. -Create new live scope manually with settings. +**Response:** -**Authentication:** Project Key required - -**Parameters:** -- `slug` - Unique scope identifier (required, alphanumeric + hyphens + underscores) -- `allowNewGenerations` - Allow new generations in scope (boolean, default: true) -- `newGenerationsLimit` - Maximum generations allowed (number, default: 30) -- `meta` - Custom metadata (JSON object, optional) - -**Purpose:** Pre-configure scope with specific settings before first use - -**Notes:** -- Scopes are typically auto-created via live URLs -- Slug must be unique within the project -- Slug format: alphanumeric + hyphens + underscores only +```json +{ + "success": true, + "data": { + "id": "550e8400-e29b-41d4-a716-446655440000", + "projectId": "57c7f7f4-47de-4d70-9ebd-3807a0b63746", + "prompt": "A photorealistic establishing shot of a sleek red sports car...", + "originalPrompt": "a red sports car on a mountain road", + "autoEnhance": true, + "aspectRatio": "16:9", + "status": "success", + "outputImageId": "7c4ccf47-41ce-4718-afbc-8c553b2c631a", + "outputImage": { + "id": "7c4ccf47-41ce-4718-afbc-8c553b2c631a", + "storageUrl": "http://localhost:9000/banatie/default/project-id/generated/image.png", + "mimeType": "image/png", + "width": 1792, + "height": 1024, + "fileSize": 1909246 + }, + "processingTimeMs": 8500, + "retryCount": 0, + "errorMessage": null, + "meta": {}, + "createdAt": "2025-11-28T10:00:00.000Z", + "updatedAt": "2025-11-28T10:00:08.500Z" + } +} +``` --- -### GET /api/v1/live/scopes +## Delete Generation -List all live scopes with pagination and statistics. +``` +DELETE /api/v1/generations/:id +``` -**Authentication:** Project Key required +Delete a generation and its output image. -**Query Parameters:** -- `slug` - Filter by exact slug match (optional) -- `limit` - Results per page (default: 20, max: 100) -- `offset` - Pagination offset (default: 0) +**Response:** `200 OK` -**Purpose:** Browse all scopes (both auto-created and manually created) - -**Returns:** Scopes with currentGenerations count, lastGeneratedAt, settings, and metadata - ---- - -### GET /api/v1/live/scopes/:slug - -Get single live scope by slug with complete statistics. - -**Authentication:** Project Key required - -**Parameters:** -- `slug` - Scope slug identifier (path parameter) - -**Purpose:** View detailed scope information including generation count, last generation timestamp, settings, and metadata - ---- - -### PUT /api/v1/live/scopes/:slug - -Update live scope settings and metadata. - -**Authentication:** Project Key required - -**Parameters:** -- `slug` - Scope slug identifier (path parameter) -- `allowNewGenerations` - Allow/disallow new generations (boolean, optional) -- `newGenerationsLimit` - Update generation limit (number, optional) -- `meta` - Update custom metadata (JSON object, optional) - -**Purpose:** Modify scope configuration - -**Notes:** -- Changes take effect immediately for new live URL requests - ---- - -### POST /api/v1/live/scopes/:slug/regenerate - -Regenerate images in a live scope. - -**Authentication:** Project Key required - -**Parameters:** -- `slug` - Scope slug identifier (path parameter) -- `imageId` - Specific image to regenerate (UUID, optional in request body) - -**Purpose:** Regenerate either a specific image or all images in the scope +```json +{ + "success": true, + "message": "Generation deleted" +} +``` **Behavior:** -- If `imageId` provided: Regenerates specific image only -- If `imageId` omitted: Regenerates all images in scope -- Uses exact same parameters (prompt, aspect ratio, etc.) -- Updates existing images (preserves IDs and URLs) -- Verifies image belongs to scope before regenerating - -**Notes:** -- Useful for refreshing stale cached images or recovering from failures +- Generation record is hard deleted +- Output image is hard deleted (unless it has a project alias) --- -### DELETE /api/v1/live/scopes/:slug - -Delete live scope with cascading image deletion. - -**Authentication:** Project Key required - -**Parameters:** -- `slug` - Scope slug identifier (path parameter) - -**Purpose:** Permanently remove scope and all cached live URL images - -**Notes:** -- Hard deletes all images in scope (MinIO + database) -- Follows alias protection rules for each image -- Hard deletes scope record (no soft delete) -- Cannot be undone - use with caution - ---- - -## Alias System - -The v1 API supports a 3-tier alias resolution system for referencing images. - -**Alias Format Requirements:** -- All aliases MUST start with `@` symbol -- Pattern: `@[a-zA-Z0-9_-]+` (alphanumeric, underscore, hyphen) -- Maximum length: 50 characters -- Examples: `@hero`, `@product-main`, `@image_1` - -**Technical Aliases** (Computed, Reserved) -- `@last` - Most recently generated image in project -- `@first` - First generated image in project -- `@upload` - Most recently uploaded image in project - -**Reserved Aliases** (Cannot be used) -- Technical aliases: `@last`, `@first`, `@upload` -- Future reserved: `@all`, `@latest`, `@oldest`, `@random`, `@next`, `@prev`, `@previous` - -**Flow Aliases** (Flow-scoped) -- Stored in flow's aliases JSONB field -- Only accessible within flow context -- Set via `PUT /api/v1/flows/:id/aliases` -- Example: `@hero` in flow A is different from `@hero` in flow B - -**Project Aliases** (Global) -- Unique within project -- Accessible across all flows -- Set via `PUT /api/v1/images/:id/alias` or generation/upload parameters -- Example: `@logo` is the same image across entire project - -**Resolution Order:** -1. Technical aliases (if matches @last, @first, @upload) -2. Flow aliases (if flowId provided in context) -3. Project aliases (global lookup) - ---- - -## Response Formats +## Response Fields ### Generation Response -Generation responses include fields for prompt enhancement tracking: +| Field | Type | Description | +|-------|------|-------------| +| `id` | string | Generation UUID | +| `projectId` | string | Project UUID | +| `prompt` | string | Prompt used for generation (enhanced if applicable) | +| `originalPrompt` | string | Original user input | +| `autoEnhance` | boolean | Whether enhancement was applied | +| `aspectRatio` | string | Image aspect ratio | +| `status` | string | Generation status | +| `outputImageId` | string | Output image UUID (when successful) | +| `outputImage` | object | Output image details (when successful) | +| `processingTimeMs` | number | Generation time in milliseconds | +| `retryCount` | number | Number of retry attempts | +| `errorMessage` | string | Error details (when failed) | +| `meta` | object | Custom metadata | +| `createdAt` | string | ISO timestamp | +| `updatedAt` | string | ISO timestamp | -```json -{ - "id": "550e8400-e29b-41d4-a716-446655440000", - "projectId": "57c7f7f4d-47de-4d70-9ebd-3807a0b63746", - "prompt": "A photorealistic establishing shot of a luxurious motor yacht...", - "originalPrompt": "шикарная моторная яхта движется по живописному озеру...", - "autoEnhance": true, - "aspectRatio": "16:9", - "status": "success", - "outputImageId": "7c4ccf47-41ce-4718-afbc-8c553b2c631a", - "outputImage": { ... }, - "referenceImages": [], - "flowId": null, - "processingTimeMs": 8980, - "cost": null, - "retryCount": 0, - "errorMessage": null, - "meta": {}, - "createdAt": "2025-11-23T13:47:00.127Z", - "updatedAt": "2025-11-23T13:47:09.107Z" -} -``` +### Output Image -**Prompt Enhancement Fields:** - -- `prompt` - The actual prompt used for generation (enhanced if `autoEnhance: true`, original if `autoEnhance: false`) -- `originalPrompt` - The user's original input prompt (always populated for transparency) -- `autoEnhance` - Boolean indicating if prompt enhancement was applied - -**Semantics:** -- When `autoEnhance: true`: - - `originalPrompt` = user's original input - - `prompt` = AI-enhanced version used for generation - - Example: Original "a cat" → Enhanced "A photorealistic close-up portrait of a cat..." - -- When `autoEnhance: false`: - - `originalPrompt` = user's original input - - `prompt` = same as originalPrompt (no enhancement) - - Both fields contain identical values - -**Default Behavior:** `autoEnhance` defaults to `true` if not explicitly set to `false`. - ---- - -### Image Response - -Image responses include actual dimensions and storage access: - -```json -{ - "id": "7c4ccf47-41ce-4718-afbc-8c553b2c631a", - "projectId": "57c7f7f4d-47de-4d70-9ebd-3807a0b63746", - "flowId": null, - "storageKey": "default/57c7f7f4d-47de-4d70-9ebd-3807a0b63746/generated/2025-11/gen_fd14839b.png", - "storageUrl": "http://localhost:9000/banatie/default/57c7f7f4d-47de-4d70-9ebd-3807a0b63746/generated/gen_fd14839b.png", - "mimeType": "image/jpeg", - "fileSize": 1909246, - "width": 1792, - "height": 1024, - "source": "generated", - "alias": null, - "focalPoint": null, - "fileHash": null, - "generationId": "fd14839b-d42e-4be9-93b3-e2fb67f7af0d", - "apiKeyId": "988cb71e-0021-4217-a536-734b097a87b3", - "meta": {}, - "createdAt": "2025-11-23T13:47:00.127Z", - "updatedAt": "2025-11-23T13:47:00.127Z", - "deletedAt": null -} -``` - -**Key Fields:** - -- `width` / `height` - Actual image dimensions in pixels (automatically extracted from generated images) -- `storageUrl` - Direct URL to access the image file (use this for image display) -- `storageKey` - Internal MinIO storage path -- `source` - Image origin: "generated" (AI-generated) or "uploaded" (user upload) -- `alias` - Project-scoped alias (e.g., "@logo"), null if not assigned -- `focalPoint` - Cropping focal point {x: 0.0-1.0, y: 0.0-1.0}, null if not set - -**Image Access:** - -To display an image, use the `storageUrl` field which provides direct access to the image file in MinIO storage. For public CDN access, use the `/cdn/:orgSlug/:projectSlug/img/:alias` endpoint. - -**Note:** Previous versions included a `url` field that pointed to a non-existent `/download` endpoint. This field has been removed. Always use `storageUrl` for direct image access. +| Field | Type | Description | +|-------|------|-------------| +| `id` | string | Image UUID | +| `storageUrl` | string | Direct URL to image file | +| `mimeType` | string | Image MIME type | +| `width` | number | Image width in pixels | +| `height` | number | Image height in pixels | +| `fileSize` | number | File size in bytes | --- ## Error Codes -| Code | Description | -|------|-------------| -| 400 | Bad Request - Invalid parameters or validation failure | -| 401 | Unauthorized - Missing, invalid, expired, or revoked API key | -| 403 | Forbidden - Scope creation disabled or insufficient permissions | -| 404 | Not Found - Resource does not exist | -| 409 | Conflict - Alias or slug already exists | -| 429 | Too Many Requests - Rate limit or scope generation limit exceeded | -| 500 | Internal Server Error - Processing or generation failure | +| HTTP Status | Code | Description | +|-------------|------|-------------| +| 400 | `VALIDATION_ERROR` | Invalid parameters | +| 401 | `UNAUTHORIZED` | Missing or invalid API key | +| 404 | `GENERATION_NOT_FOUND` | Generation does not exist | +| 429 | `RATE_LIMIT_EXCEEDED` | Too many requests | +| 500 | `GENERATION_FAILED` | AI generation failed | -**Common Error Codes:** -- `VALIDATION_ERROR` - Invalid parameters or missing required fields -- `GENERATION_NOT_FOUND` - Generation does not exist -- `FLOW_NOT_FOUND` - Flow does not exist -- `IMAGE_NOT_FOUND` - Image or alias not found -- `ALIAS_CONFLICT` - Alias already exists in project -- `ALIAS_NOT_FOUND` - Alias does not exist in any scope -- `SCOPE_INVALID_FORMAT` - Invalid scope slug format -- `SCOPE_ALREADY_EXISTS` - Scope slug already in use -- `SCOPE_NOT_FOUND` - Scope does not exist -- `SCOPE_CREATION_DISABLED` - New scope creation not allowed -- `SCOPE_GENERATION_LIMIT_EXCEEDED` - Scope generation limit reached -- `IP_RATE_LIMIT_EXCEEDED` - IP rate limit exceeded for live URLs -- `ORG_NOT_FOUND` - Organization not found -- `PROJECT_NOT_FOUND` - Project not found -- `IMAGE_NOT_IN_SCOPE` - Image doesn't belong to specified scope +--- -**Common Error Messages:** -- `"Prompt is required"` - Missing prompt parameter -- `"Alias already exists"` - Alias conflict in project -- `"Invalid aspect ratio"` - Unsupported aspect ratio format -- `"File too large"` - Upload exceeds 5MB limit -- `"alias_format_check"` - Alias must start with @ symbol (e.g., @hero not hero) -- `"Rate limit exceeded. Try again in N seconds"` - IP rate limit for live URLs +## Rate Limits + +- **100 requests per hour** per API key +- Rate limit headers included in response: + - `X-RateLimit-Limit`: Maximum requests + - `X-RateLimit-Remaining`: Remaining requests + - `X-RateLimit-Reset`: Seconds until reset + +--- + +## See Also + +- [Advanced Generation](image-generation-advanced.md) - References, aliases, flows +- [Image Upload](images-upload.md) - Upload and manage images +- [Live URLs](live-url.md) - CDN and live generation diff --git a/docs/api/images-upload.md b/docs/api/images-upload.md new file mode 100644 index 0000000..26c6d53 --- /dev/null +++ b/docs/api/images-upload.md @@ -0,0 +1,374 @@ +# Image Upload & Management API + +Upload images and manage your image library. For generation, see [image-generation.md](image-generation.md). + +All endpoints require Project Key authentication via `X-API-Key` header. + +--- + +## Upload Image + +``` +POST /api/v1/images/upload +``` + +Upload an image file with optional alias and flow association. + +**Content-Type:** `multipart/form-data` + +**Form Parameters:** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `file` | file | Yes | Image file (PNG, JPEG, WebP) | +| `alias` | string | No | Project-scoped alias (e.g., `@logo`) | +| `flowId` | string | No | Flow UUID to associate with | +| `flowAlias` | string | No | Flow-scoped alias (requires flowId) | +| `meta` | string | No | JSON string with custom metadata | + +**File Constraints:** + +| Constraint | Limit | +|------------|-------| +| Max file size | 5MB | +| Supported formats | PNG, JPEG, JPG, WebP | +| MIME types | `image/png`, `image/jpeg`, `image/webp` | + +**Example Request (curl):** + +```bash +curl -X POST http://localhost:3000/api/v1/images/upload \ + -H "X-API-Key: YOUR_PROJECT_KEY" \ + -F "file=@logo.png" \ + -F "alias=@brand-logo" \ + -F 'meta={"tags": ["logo", "brand"]}' +``` + +**Response:** `201 Created` + +```json +{ + "success": true, + "data": { + "id": "7c4ccf47-41ce-4718-afbc-8c553b2c631a", + "projectId": "57c7f7f4-47de-4d70-9ebd-3807a0b63746", + "flowId": null, + "storageKey": "default/project-id/uploads/2025-11/logo.png", + "storageUrl": "http://localhost:9000/banatie/default/project-id/uploads/logo.png", + "mimeType": "image/png", + "fileSize": 45678, + "width": 512, + "height": 512, + "source": "uploaded", + "alias": "@brand-logo", + "focalPoint": null, + "meta": { "tags": ["logo", "brand"] }, + "createdAt": "2025-11-28T10:00:00.000Z" + } +} +``` + +### flowId Behavior + +| Value | Behavior | +|-------|----------| +| Not provided | Auto-generate `pendingFlowId`, lazy flow creation | +| `null` | No flow association | +| `"uuid"` | Associate with specified flow | + +### Upload with Flow + +```bash +# Associate with existing flow +curl -X POST .../images/upload \ + -F "file=@reference.png" \ + -F "flowId=flow-123" \ + -F "flowAlias=@reference" +``` + +--- + +## List Images + +``` +GET /api/v1/images +``` + +List all images with filtering and pagination. + +**Query Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `flowId` | string | - | Filter by flow UUID | +| `source` | string | - | Filter by source: `generated`, `uploaded` | +| `alias` | string | - | Filter by exact alias match | +| `limit` | number | `20` | Results per page (max: 100) | +| `offset` | number | `0` | Pagination offset | +| `includeDeleted` | boolean | `false` | Include soft-deleted records | + +**Example:** + +``` +GET /api/v1/images?source=uploaded&limit=10 +``` + +**Response:** + +```json +{ + "success": true, + "data": [ + { + "id": "7c4ccf47-...", + "storageUrl": "http://...", + "source": "uploaded", + "alias": "@brand-logo", + "width": 512, + "height": 512, + "createdAt": "2025-11-28T10:00:00.000Z" + } + ], + "pagination": { + "limit": 10, + "offset": 0, + "total": 25, + "hasMore": true + } +} +``` + +--- + +## Get Image + +``` +GET /api/v1/images/:id_or_alias +``` + +Get a single image by UUID or alias. + +**Path Parameter:** +- `id_or_alias` - Image UUID or `@alias` + +**Query Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `flowId` | string | Flow context for alias resolution | + +**Examples:** + +``` +# By UUID +GET /api/v1/images/7c4ccf47-41ce-4718-afbc-8c553b2c631a + +# By project alias +GET /api/v1/images/@brand-logo + +# By technical alias (requires flowId) +GET /api/v1/images/@last?flowId=flow-123 + +# By flow alias +GET /api/v1/images/@hero?flowId=flow-123 +``` + +**Response:** + +```json +{ + "success": true, + "data": { + "id": "7c4ccf47-41ce-4718-afbc-8c553b2c631a", + "projectId": "57c7f7f4-47de-4d70-9ebd-3807a0b63746", + "flowId": null, + "storageKey": "default/project-id/uploads/2025-11/logo.png", + "storageUrl": "http://localhost:9000/banatie/.../logo.png", + "mimeType": "image/png", + "fileSize": 45678, + "width": 512, + "height": 512, + "source": "uploaded", + "alias": "@brand-logo", + "focalPoint": null, + "fileHash": null, + "generationId": null, + "meta": { "tags": ["logo", "brand"] }, + "createdAt": "2025-11-28T10:00:00.000Z", + "updatedAt": "2025-11-28T10:00:00.000Z", + "deletedAt": null + } +} +``` + +--- + +## Update Image Metadata + +``` +PUT /api/v1/images/:id_or_alias +``` + +Update image metadata (focal point, custom metadata). + +**Request Body:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `focalPoint` | object | Focal point: `{ x: 0.0-1.0, y: 0.0-1.0 }` | +| `meta` | object | Custom metadata | + +**Example:** + +```json +// PUT /api/v1/images/@brand-logo +{ + "focalPoint": { "x": 0.5, "y": 0.3 }, + "meta": { + "description": "Updated brand logo", + "tags": ["logo", "brand", "2025"] + } +} +``` + +**Response:** Updated image object. + +> **Note:** Alias assignment has its own dedicated endpoint. + +--- + +## Assign Alias + +``` +PUT /api/v1/images/:id_or_alias/alias +``` + +Assign or remove a project-scoped alias. + +**Request Body:** + +```json +// Assign alias +{ "alias": "@new-logo" } + +// Remove alias +{ "alias": null } +``` + +**Override Behavior:** +- If another image has this alias, it loses the alias +- The new image gets the alias +- Old image is preserved, just unlinked + +**Example:** + +```bash +curl -X PUT http://localhost:3000/api/v1/images/7c4ccf47-.../alias \ + -H "X-API-Key: YOUR_KEY" \ + -H "Content-Type: application/json" \ + -d '{"alias": "@primary-logo"}' +``` + +--- + +## Delete Image + +``` +DELETE /api/v1/images/:id_or_alias +``` + +Permanently delete an image and its storage file. + +**Behavior:** +- **Hard delete** - image record permanently removed +- Storage file deleted from MinIO +- Cascading updates: + - Related generations: `outputImageId` set to null + - Flow aliases: image removed from flow's aliases + - Referenced images: removed from generation's referencedImages + +**Response:** `200 OK` + +```json +{ + "success": true, + "message": "Image deleted" +} +``` + +> **Warning:** This cannot be undone. The image file is permanently removed. + +--- + +## Image Response Fields + +| Field | Type | Description | +|-------|------|-------------| +| `id` | string | Image UUID | +| `projectId` | string | Project UUID | +| `flowId` | string | Associated flow UUID (null if none) | +| `storageKey` | string | Internal storage path | +| `storageUrl` | string | **Direct URL to access image** | +| `mimeType` | string | Image MIME type | +| `fileSize` | number | File size in bytes | +| `width` | number | Image width in pixels | +| `height` | number | Image height in pixels | +| `source` | string | `"generated"` or `"uploaded"` | +| `alias` | string | Project-scoped alias (null if none) | +| `focalPoint` | object | `{ x, y }` coordinates (0.0-1.0) | +| `fileHash` | string | SHA-256 hash for deduplication | +| `generationId` | string | Source generation UUID (if generated) | +| `meta` | object | Custom metadata | +| `createdAt` | string | ISO timestamp | +| `updatedAt` | string | ISO timestamp | +| `deletedAt` | string | Soft delete timestamp (null if active) | + +### Accessing Images + +Use `storageUrl` for direct image access: + +```html + +``` + +For public CDN access, see [Live URLs](live-url.md). + +--- + +## Storage Organization + +Images are organized in MinIO storage: + +``` +bucket/ + {orgId}/ + {projectId}/ + uploads/ # Uploaded images + 2025-11/ + image.png + generated/ # AI-generated images + 2025-11/ + gen_abc123.png +``` + +--- + +## Error Codes + +| HTTP Status | Code | Description | +|-------------|------|-------------| +| 400 | `VALIDATION_ERROR` | Invalid parameters | +| 400 | `FILE_TOO_LARGE` | File exceeds 5MB limit | +| 400 | `UNSUPPORTED_FILE_TYPE` | Not PNG, JPEG, or WebP | +| 400 | `ALIAS_FORMAT_CHECK` | Alias must start with @ | +| 401 | `UNAUTHORIZED` | Missing or invalid API key | +| 404 | `IMAGE_NOT_FOUND` | Image or alias doesn't exist | +| 404 | `ALIAS_NOT_FOUND` | Alias doesn't resolve to any image | + +--- + +## See Also + +- [Basic Generation](image-generation.md) - Generate images +- [Advanced Generation](image-generation-advanced.md) - References, aliases, flows +- [Live URLs](live-url.md) - CDN and public access diff --git a/docs/api/live-url.md b/docs/api/live-url.md new file mode 100644 index 0000000..b902eb8 --- /dev/null +++ b/docs/api/live-url.md @@ -0,0 +1,380 @@ +# Live URL & CDN API + +Public CDN endpoints for image serving and live URL generation. For authenticated API, see [image-generation.md](image-generation.md). + +--- + +## CDN Image Serving + +``` +GET /cdn/:orgSlug/:projectSlug/img/:filenameOrAlias +``` + +**Authentication:** None - Public endpoint + +Serve images by filename or project-scoped alias. + +**Path Parameters:** + +| Parameter | Description | +|-----------|-------------| +| `orgSlug` | Organization identifier | +| `projectSlug` | Project identifier | +| `filenameOrAlias` | Filename or `@alias` | + +**Examples:** + +``` +# By filename +GET /cdn/acme/website/img/hero-background.jpg + +# By alias +GET /cdn/acme/website/img/@hero +``` + +**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 + +``` + +First load generates, subsequent loads are cached. + +### Product Placeholders + +```html + +``` + +### Blog Post Images + +```html + +``` + +--- + +## 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