banatie-service/tests/api/api-requirements-v2.md

15 KiB

Banatie REST API Implementation Plan

Version: 2.0
Status: Ready for Implementation
Executor: Claude Code
Database Schema: v2.0 (banatie-database-design.md)


Overview

REST API for Banatie image generation service. All endpoints use /api/v1/ prefix for versioning.

Core Features:

  • AI image generation with Google Gemini Flash
  • Dual alias system (project-scoped + flow-scoped)
  • Technical aliases (@last, @first, @upload)
  • Flow-based generation chains
  • Live generation endpoint with caching
  • Upload and reference images

Authentication: API keys only (bnt_ prefix)


Authentication

All endpoints require API key in header:

X-API-Key: bnt_xxx...

API Key Types:

  • master: Full access to all projects in organization
  • project: Access to specific project only

Unauthorized Response (401):

{
  "error": "Unauthorized",
  "message": "Invalid or missing API key"
}

Implementation Phases

Phase 1: Foundation

Goal: Core utilities and services

Tasks:

  • Create TypeScript type definitions for all models
  • Build validation utilities (alias format, pagination, query params)
  • Build helper utilities (pagination, hash, query helpers)
  • Create AliasService with 3-tier resolution (technical → flow → project)

Git Commit:

feat: add foundation utilities and alias service

Phase 2: Core Generation Flow

Goal: Main generation endpoints

Services:

  • ImageService - CRUD operations with soft delete
  • GenerationService - Full lifecycle management

Endpoints:

  • POST /api/v1/generations - Create with reference images & dual aliases
  • GET /api/v1/generations - List with filters
  • GET /api/v1/generations/:id - Get details with related data

Git Commit:

feat: implement core generation endpoints

Phase 3: Flow Management

Goal: Flow operations

Services:

  • FlowService - CRUD with computed counts & alias management

Endpoints:

  • POST /api/v1/flows - Create flow
  • GET /api/v1/flows - List flows with computed counts
  • GET /api/v1/flows/:id - Get details with generations and images
  • PUT /api/v1/flows/:id/aliases - Update flow aliases
  • DELETE /api/v1/flows/:id/aliases/:alias - Remove specific alias
  • DELETE /api/v1/flows/:id - Delete flow

Git Commit:

feat: implement flow management endpoints

Phase 4: Enhanced Image Management

Goal: Complete image operations

Endpoints:

  • POST /api/v1/images/upload - Upload with alias, flow, metadata
  • GET /api/v1/images - List with filters
  • GET /api/v1/images/:id - Get details with usage info
  • GET /api/v1/images/resolve/:alias - Resolve alias with precedence
  • PUT /api/v1/images/:id - Update metadata
  • DELETE /api/v1/images/:id - Soft/hard delete

Git Commit:

feat: implement image management endpoints

Phase 5: Generation Refinements

Goal: Additional generation operations

Endpoints:

  • POST /api/v1/generations/:id/retry - Retry failed generation
  • DELETE /api/v1/generations/:id - Delete generation

Git Commit:

feat: add generation retry and delete endpoints

Phase 6: Live Generation

Goal: URL-based generation with caching

Services:

  • PromptCacheService - SHA-256 caching with hit tracking

Endpoints:

  • GET /api/v1/live - Generate image via URL with streaming proxy

Important: Stream image directly from MinIO (no 302 redirect) for better performance.

Git Commit:

feat: implement live generation endpoint with caching

Phase 7: Analytics

Goal: Project statistics and metrics

Services:

  • AnalyticsService - Aggregation queries

Endpoints:

  • GET /api/v1/analytics/summary - Project statistics
  • GET /api/v1/analytics/generations/timeline - Time-series data

Git Commit:

feat: add analytics endpoints

Phase 8: Testing & Documentation

Goal: Quality assurance

Tasks:

  • Unit tests for all services (target >80% coverage)
  • Integration tests for critical flows
  • Error handling consistency review
  • Update API documentation

Git Commit:

test: add comprehensive test coverage and documentation

API Endpoints Specification

GENERATIONS

POST /api/v1/generations

Create new image generation.

Request Body:

{
  prompt: string;                    // Required: 1-2000 chars
  aspectRatio?: string;              // Optional: '16:9', '1:1', '4:3', '9:16'
  width?: number;                    // Optional: 1-8192
  height?: number;                   // Optional: 1-8192
  referenceImages?: string[];        // Optional: ['@logo', '@product', '@last']
  flowId?: string;                   // Optional: Add to existing flow
  assignAlias?: string;              // Optional: Project-scoped alias '@brand'
  assignFlowAlias?: string;          // Optional: Flow-scoped alias '@hero' (requires flowId)
  meta?: Record<string, unknown>;
}

Response (200):

{
  generation: Generation;
  image?: Image;                     // If generation completed
}

Errors: 400, 401, 404, 422, 429, 500


GET /api/v1/generations

List generations with filtering.

Query Params:

{
  flowId?: string;
  status?: 'pending' | 'processing' | 'success' | 'failed';
  limit?: number;                    // Default: 20, max: 100
  offset?: number;                   // Default: 0
  sortBy?: 'createdAt' | 'updatedAt';
  order?: 'asc' | 'desc';            // Default: desc
}

Response (200):

{
  generations: Generation[];
  pagination: PaginationInfo;
}

GET /api/v1/generations/:id

Get generation details.

Response (200):

{
  generation: Generation;
  image?: Image;
  referencedImages: Image[];
  flow?: FlowSummary;
}

POST /api/v1/generations/:id/retry

Retry failed generation.

Response (200):

{
  generation: Generation;            // New generation with incremented retry_count
}

Errors: 404, 422


DELETE /api/v1/generations/:id

Delete generation.

Query Params:

{
  hard?: boolean;                    // Default: false
}

Response (204): No content


IMAGES

POST /api/v1/images/upload

Upload image file.

Request: multipart/form-data

Fields:

{
  file: File;                        // Required, max 5MB
  alias?: string;                    // Project-scoped: '@logo'
  flowAlias?: string;                // Flow-scoped: '@hero' (requires flowId)
  flowId?: string;
  description?: string;
  tags?: string[];                   // JSON array as string
  focalPoint?: string;               // JSON: '{"x":0.5,"y":0.5}'
  meta?: string;                     // JSON object as string
}

Response (201):

{
  image: Image;
  flow?: FlowSummary;                // If flowAlias assigned
}

Errors: 400, 409, 422


GET /api/v1/images

List images.

Query Params:

{
  flowId?: string;
  source?: 'generated' | 'uploaded';
  alias?: string;
  limit?: number;                    // Default: 20, max: 100
  offset?: number;
  sortBy?: 'createdAt' | 'fileSize';
  order?: 'asc' | 'desc';
}

Response (200):

{
  images: Image[];
  pagination: PaginationInfo;
}

GET /api/v1/images/:id

Get image details.

Response (200):

{
  image: Image;
  generation?: Generation;
  usedInGenerations: GenerationSummary[];
}

GET /api/v1/images/resolve/:alias

Resolve alias to image.

Query Params:

{
  flowId?: string;                   // Provide flow context
}

Response (200):

{
  image: Image;
  scope: 'flow' | 'project' | 'technical';
  flow?: FlowSummary;
}

Resolution Order:

  1. Technical aliases (@last, @first, @upload) if flowId provided
  2. Flow aliases from flows.aliases if flowId provided
  3. Project aliases from images.alias

Errors: 404


PUT /api/v1/images/:id

Update image metadata.

Request Body:

{
  alias?: string;
  description?: string;
  tags?: string[];
  focalPoint?: { x: number; y: number };
  meta?: Record<string, unknown>;
}

Response (200):

{
  image: Image;
}

Errors: 404, 409, 422


DELETE /api/v1/images/:id

Delete image.

Query Params:

{
  hard?: boolean;                    // Default: false
}

Response (204): No content


FLOWS

POST /api/v1/flows

Create new flow.

Request Body:

{
  meta?: Record<string, unknown>;
}

Response (201):

{
  flow: Flow;
}

GET /api/v1/flows

List flows.

Query Params:

{
  limit?: number;                    // Default: 20, max: 100
  offset?: number;
  sortBy?: 'createdAt' | 'updatedAt';
  order?: 'asc' | 'desc';
}

Response (200):

{
  flows: Array<Flow & {
    generationCount: number;         // Computed
    imageCount: number;              // Computed
  }>;
  pagination: PaginationInfo;
}

GET /api/v1/flows/:id

Get flow details.

Response (200):

{
  flow: Flow;
  generations: Generation[];         // Ordered by created_at ASC
  images: Image[];
  resolvedAliases: Record<string, Image>;
}

PUT /api/v1/flows/:id/aliases

Update flow aliases.

Request Body:

{
  aliases: Record<string, string>;   // { "@hero": "image-uuid" }
}

Response (200):

{
  flow: Flow;
}

Validation:

  • Keys must match ^@[a-zA-Z0-9_-]+$
  • Values must be valid image UUIDs
  • Cannot use reserved: @last, @first, @upload

Errors: 404, 422


DELETE /api/v1/flows/:id/aliases/:alias

Remove specific alias from flow.

Response (204): No content

Errors: 404


DELETE /api/v1/flows/:id

Delete flow.

Response (204): No content

Note: Cascades to images, sets NULL on generations.flow_id


LIVE GENERATION

GET /api/v1/live

Generate image via URL with caching and streaming.

Query Params:

{
  prompt: string;                    // Required
  aspectRatio?: string;
  width?: number;
  height?: number;
  reference?: string | string[];     // '@logo' or ['@logo','@style']
}

Response: Image stream with headers

Headers:

Content-Type: image/jpeg
Cache-Control: public, max-age=31536000
X-Cache-Status: HIT | MISS

Implementation:

  1. Compute cache key: SHA256(prompt + sorted params)
  2. Check prompt_url_cache table
  3. If HIT: increment hit_count, stream from MinIO
  4. If MISS: generate, cache, stream from MinIO
  5. Stream image bytes directly (no 302 redirect)

Errors: 400, 404, 500


ANALYTICS

GET /api/v1/analytics/summary

Get project statistics.

Query Params:

{
  startDate?: string;                // ISO 8601
  endDate?: string;
  flowId?: string;
}

Response (200):

{
  period: { startDate: string; endDate: string };
  metrics: {
    totalGenerations: number;
    successfulGenerations: number;
    failedGenerations: number;
    successRate: number;
    totalImages: number;
    uploadedImages: number;
    generatedImages: number;
    avgProcessingTimeMs: number;
    totalCacheHits: number;
    cacheHitRate: number;
    totalCost: number;
  };
  flows: FlowSummary[];
}

GET /api/v1/analytics/generations/timeline

Get generation statistics over time.

Query Params:

{
  startDate?: string;
  endDate?: string;
  flowId?: string;
  groupBy?: 'hour' | 'day' | 'week'; // Default: day
}

Response (200):

{
  data: Array<{
    timestamp: string;
    total: number;
    successful: number;
    failed: number;
    avgProcessingTimeMs: number;
  }>;
}

Implementation Guidelines

Alias Resolution Algorithm

Priority Order:

  1. Technical aliases (@last, @first, @upload) - compute from flow data
  2. Flow-scoped aliases - from flows.aliases JSONB
  3. Project-scoped aliases - from images.alias column

Technical Aliases:

  • @last: Latest generation output in flow (any status)
  • @first: First generation output in flow
  • @upload: Latest uploaded image in flow

Dual Alias Assignment

When creating generation or uploading image:

  • assignAlias → set images.alias (project scope)
  • assignFlowAlias → add to flows.aliases (flow scope)
  • Both can be assigned simultaneously

Flow Updates

Update flows.updated_at on:

  • New generation created with flowId
  • New image uploaded with flowId
  • Flow aliases modified

Audit Trail

Track api_key_id in:

  • images.api_key_id - who uploaded/generated
  • generations.api_key_id - who requested

Rate Limiting

In-memory rate limiting (defer Redis for MVP):

  • Master key: 1000 req/hour, 100 generations/hour
  • Project key: 500 req/hour, 50 generations/hour

Headers:

X-RateLimit-Limit: 500
X-RateLimit-Remaining: 487
X-RateLimit-Reset: 1698765432

Error Response Format

{
  error: string;
  message: string;
  details?: unknown;
  requestId?: string;
}

MinIO Integration

Use streaming for /api/v1/live:

const stream = await minioClient.getObject(bucket, storageKey);
res.set('Content-Type', mimeType);
stream.pipe(res);

Generate presigned URLs for other endpoints:

const url = await minioClient.presignedGetObject(bucket, storageKey, 24 * 60 * 60);

Validation Rules

Alias Format:

  • Pattern: ^@[a-zA-Z0-9_-]+$
  • Reserved: @last, @first, @upload
  • Length: 3-100 chars

File Upload:

  • Max size: 5MB
  • MIME types: image/jpeg, image/png, image/webp
  • Max dimensions: 8192x8192

Prompt:

  • Min: 1 char
  • Max: 2000 chars

Aspect Ratio:

  • Pattern: ^\d+:\d+$
  • Examples: 16:9, 1:1, 4:3, 9:16

Service Architecture

Core Services

AliasService:

  • Resolve aliases with 3-tier precedence
  • Compute technical aliases
  • Validate alias format

ImageService:

  • CRUD operations
  • Soft delete support
  • Usage tracking

GenerationService:

  • Generation lifecycle
  • Status transitions
  • Error handling
  • Retry logic

FlowService:

  • Flow CRUD
  • Alias management
  • Computed counts

PromptCacheService:

  • Cache key computation (SHA-256)
  • Hit tracking
  • Cache lookup

AnalyticsService:

  • Aggregation queries
  • Time-series grouping

Reusable Utilities

Validators:

  • Alias format
  • Pagination params
  • Query filters

Helpers:

  • Pagination builder
  • SHA-256 hashing
  • Query helpers

Testing Requirements

Unit Tests:

  • All services must have unit tests
  • Target coverage: >80%
  • Mock database calls

Integration Tests:

  • Critical flows end-to-end
  • Real database transactions
  • API endpoint testing with supertest

Test Scenarios:

  • Alias resolution precedence
  • Flow-scoped vs project-scoped aliases
  • Technical alias computation
  • Dual alias assignment
  • Cache hit/miss behavior
  • Error handling
  • Rate limiting

Success Criteria

All endpoints functional per specification
>80% test coverage on services
Consistent error handling across all endpoints
All validation rules implemented
Rate limiting working
Documentation updated
Git commits after each phase


Document Version: 2.0
Created: 2025-11-09
Target: Claude Code Implementation
Database Schema: v2.0