openapi: 3.0.3 info: title: FlatRender Render Orchestrator (internal) version: 1.0.0 description: | Render job orchestration, node pool management, snapshots, exports. Owned by Go service. The browser connects to WebSocket via Gateway for live progress. servers: - url: http://render-svc.internal/v1 security: - BearerAuth: [] - ServiceToken: [] tags: - name: Jobs - name: Snapshots - name: Exports - name: Nodes - name: Admin - name: Internal paths: # ===================== JOBS ===================== /renders: get: tags: [Jobs] summary: List user's render jobs parameters: - { name: status, in: query, schema: { type: string } } - { name: page, in: query, schema: { type: integer } } - { name: page_size, in: query, schema: { type: integer } } responses: '200': content: application/json: schema: type: object properties: data: { type: array, items: { $ref: '#/components/schemas/RenderJob' } } meta: { $ref: '#/components/schemas/PaginationMeta' } post: tags: [Jobs] summary: Submit new render job requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/RenderJobCreate' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/RenderJob' } '402': { description: Insufficient balance/charge } /renders/{job_id}: get: tags: [Jobs] parameters: - { name: job_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/RenderJobDetail' } /renders/{job_id}/cancel: post: tags: [Jobs] parameters: - { name: job_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: content: application/json: schema: type: object properties: reason: { type: string } responses: '200': content: application/json: schema: type: object properties: cancelled: { type: boolean } refund_amount_minor: { type: integer, format: int64 } /renders/{job_id}/retry: post: tags: [Jobs] summary: Retry a failed render (uses same config) parameters: - { name: job_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/RenderJob' } /renders/{job_id}/progress: get: tags: [Jobs] summary: | Fallback polling endpoint when WebSocket isn't usable. Browser primary is WS at /ws/v1/render/{job_id}. parameters: - { name: job_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/RenderProgress' } /renders/{job_id}/logs: get: tags: [Jobs] summary: Get render execution logs (admin or owner) parameters: - { name: job_id, in: path, required: true, schema: { type: string, format: uuid } } - { name: level, in: query, schema: { type: string, enum: [debug, info, warn, error] } } responses: '200': content: application/json: schema: type: object properties: logs: type: array items: type: object properties: timestamp: { type: string, format: date-time } level: { type: string } node_id: { type: string, format: uuid, nullable: true } message: { type: string } meta: { type: object } # ===================== SNAPSHOTS ===================== /snapshots: post: tags: [Snapshots] summary: Request single-frame snapshot of a scene requestBody: required: true content: application/json: schema: type: object required: [saved_project_id, scene_key, frame_number] properties: saved_project_id: { type: string, format: uuid } scene_key: { type: string } frame_number: { type: integer, minimum: 0 } responses: '202': description: Snapshot queued (or returned immediately if cached) content: application/json: schema: type: object properties: snapshot_id: { type: string, format: uuid } status: { type: string, enum: [Cached, Pending, Rendering] } image_url: { type: string, nullable: true } thumbnail_url: { type: string, nullable: true } expires_at: { type: string, format: date-time, nullable: true } /snapshots/{snapshot_id}: get: tags: [Snapshots] parameters: - { name: snapshot_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/Snapshot' } # ===================== EXPORTS ===================== /exports: get: tags: [Exports] summary: List user's exports parameters: - { name: page, in: query, schema: { type: integer } } responses: '200': content: application/json: schema: type: object properties: data: { type: array, items: { $ref: '#/components/schemas/Export' } } meta: { $ref: '#/components/schemas/PaginationMeta' } /exports/{export_id}: get: tags: [Exports] parameters: - { name: export_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/ExportDetail' } delete: tags: [Exports] parameters: - { name: export_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '204': { description: Deleted } /exports/{export_id}/extend: post: tags: [Exports] summary: Extend auto-delete date (paid feature) parameters: - { name: export_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object properties: days: { type: integer, minimum: 1, maximum: 365 } responses: '200': content: application/json: schema: type: object properties: new_auto_delete_date: { type: string, format: date-time } /exports/{export_id}/download-url: get: tags: [Exports] summary: Get presigned MinIO URL (short-lived) parameters: - { name: export_id, in: path, required: true, schema: { type: string, format: uuid } } - { name: format, in: query, schema: { type: string, default: mp4 } } responses: '200': content: application/json: schema: type: object properties: url: { type: string } expires_at: { type: string, format: date-time } # ===================== NODES (admin) ===================== /nodes: get: tags: [Nodes] summary: List nodes parameters: - { name: region, in: query, schema: { type: string } } - { name: status, in: query, schema: { type: string } } responses: '200': content: application/json: schema: type: object properties: data: { type: array, items: { $ref: '#/components/schemas/RenderNode' } } post: tags: [Nodes] summary: (Admin) Register new node requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/RenderNodeCreate' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/RenderNode' } /nodes/{node_id}: get: tags: [Nodes] parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/RenderNodeDetail' } patch: tags: [Nodes] parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object properties: priority: { type: integer } is_active: { type: boolean } accepts_new_jobs: { type: boolean } node_kind: { type: string } owner_user_id: { type: string, format: uuid } next_maintenance_at: { type: string, format: date-time } maintenance_reason: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/RenderNode' } /nodes/{node_id}/restart: post: tags: [Nodes] summary: Reboot node OS parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '202': { description: Restart issued } /nodes/{node_id}/release: post: tags: [Nodes] summary: Force-release node from any current job parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '204': { description: Released } /nodes/{node_id}/close-ae: post: tags: [Nodes] summary: Force-kill AfterFX on a node parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '204': { description: AE closed } /nodes/{node_id}/health: get: tags: [Nodes] summary: Current node health parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/NodeHealth' } /nodes/{node_id}/health/history: get: tags: [Nodes] parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } - { name: from, in: query, schema: { type: string, format: date-time } } - { name: to, in: query, schema: { type: string, format: date-time } } responses: '200': content: application/json: schema: type: object properties: data: type: array items: { $ref: '#/components/schemas/NodeHealth' } /nodes/{node_id}/crashes: get: tags: [Nodes] parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: type: object properties: data: { type: array, items: { $ref: '#/components/schemas/NodeCrash' } } /node-updates: get: tags: [Nodes] summary: Available node software updates responses: '200': content: application/json: schema: type: object properties: data: { type: array, items: { $ref: '#/components/schemas/NodeUpdate' } } /node-updates/{update_id}/rollout: post: tags: [Nodes] summary: Trigger rollout to selected nodes parameters: - { name: update_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object properties: node_ids: { type: array, items: { type: string, format: uuid } } responses: '202': { description: Rollout queued } # ===================== INTERNAL (called by node agents) ===================== /internal/nodes/{node_id}/heartbeat: post: tags: [Internal] summary: Node sends heartbeat (every 5s) security: [NodeHmac: []] parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/NodeHeartbeat' } responses: '200': content: application/json: schema: type: object properties: next_heartbeat_in_sec: { type: integer } pending_commands: type: array description: e.g. "cancel current job", "update software" items: { type: object } /internal/nodes/{node_id}/online: post: tags: [Internal] summary: Node agent reports it just started security: [NodeHmac: []] parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object properties: node_agent_version: { type: string } current_ae_version: { type: string } available_ae_versions: { type: array, items: { type: string } } ram_gb: { type: integer } cpu_cores: { type: integer } cache_used_gb: { type: integer } cached_template_md5s: { type: array, items: { type: string } } responses: '200': { description: Acknowledged } /internal/render/jobs/{job_id}/frames: post: tags: [Internal] summary: Node pushes per-frame progress security: [NodeHmac: []] parameters: - { name: job_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object required: [frame_job_id, frame_number] properties: frame_job_id: { type: string, format: uuid } frame_number: { type: integer } file_size_bytes: { type: integer } completed_at: { type: string, format: date-time } responses: '204': { description: Recorded } /internal/render/jobs/{job_id}/crash: post: tags: [Internal] summary: Node reports an AE crash on this job security: [NodeHmac: []] parameters: - { name: job_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object required: [node_id] properties: node_id: { type: string, format: uuid } frame_job_id: { type: string, format: uuid } last_known_frame: { type: integer } crash_signal: { type: string } ae_version: { type: string } error_log_tail: { type: string } log_file_url: { type: string } responses: '200': content: application/json: schema: type: object properties: action_recommended: type: string enum: [ResetAndRestart, ReassignWork, RestartNode] reassigned_to_node_id: type: string format: uuid nullable: true /internal/render/jobs/{job_id}/replica-ready: post: tags: [Internal] summary: Node reports replica .aep saved (after JSX run) security: [NodeHmac: []] parameters: - { name: job_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object required: [node_id, replica_path] properties: node_id: { type: string, format: uuid } replica_path: { type: string } replica_md5: { type: string } responses: '204': { description: Acknowledged } /internal/nodes/{node_id}/cache-update: post: tags: [Internal] summary: Node reports a template cache change security: [NodeHmac: []] parameters: - { name: node_id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object required: [action, aep_file_md5] properties: action: { type: string, enum: [Downloaded, Evicted, Verified, Failed] } project_id: { type: string, format: uuid } aep_file_md5: { type: string } file_size_bytes: { type: integer, format: int64 } cache_used_gb: { type: integer } error_message: { type: string } responses: '204': { description: Recorded } components: securitySchemes: BearerAuth: { type: http, scheme: bearer, bearerFormat: JWT } ServiceToken: { type: http, scheme: bearer } NodeHmac: type: apiKey in: header name: X-Node-Signature schemas: PaginationMeta: type: object properties: page: { type: integer } page_size: { type: integer } total: { type: integer } has_more: { type: boolean } RenderJob: type: object properties: id: { type: string, format: uuid } saved_project_id: { type: string, format: uuid } name: { type: string } step: { type: string } render_progress: { type: integer } priority_queue: { type: string } price_type: { type: string } paid_price_minor: { type: integer, format: int64 } quality: { type: string } resolution: { type: string } frame_rate: { type: integer } duration_sec: { type: number } has_voiceover: { type: boolean } image_preview_b64: { type: string, nullable: true } failed_message: { type: string, nullable: true } export_id: { type: string, format: uuid, nullable: true } queued_at: { type: string, format: date-time } started_at: { type: string, format: date-time, nullable: true } completed_at: { type: string, format: date-time, nullable: true } RenderJobCreate: type: object required: [saved_project_id, quality, resolution] properties: saved_project_id: { type: string, format: uuid } quality: { type: string, enum: [Low, Medium, High, Full, Lossless] } resolution: { type: string, enum: [HD, FullHD, TwoK, FourK] } frame_rate: { type: integer, enum: [24, 25, 30, 60] } is_60_fps: { type: boolean } price_type: type: string enum: [Free, Preview, Cash, Plan, Snapshot, Reseller] discount_code: { type: string } support_flatrender: { type: boolean } tell_me_when_done: { type: boolean } preferred_region: { type: string } RenderJobDetail: allOf: - $ref: '#/components/schemas/RenderJob' - type: object properties: frame_jobs: type: array items: { $ref: '#/components/schemas/FrameJob' } retry_count: { type: integer } repair_attempts: { type: integer } export: { $ref: '#/components/schemas/Export', nullable: true } FrameJob: type: object properties: id: { type: string, format: uuid } node_id: { type: string, format: uuid } start_frame: { type: integer } end_frame: { type: integer } order_value: { type: integer } status: { type: string } frames_rendered: { type: integer } frames_validated: { type: integer } attempt: { type: integer } last_error: { type: string, nullable: true } RenderProgress: type: object properties: job_id: { type: string, format: uuid } step: { type: string } progress: { type: integer } current_frame: { type: integer, nullable: true } total_frames: { type: integer, nullable: true } eta_seconds: { type: integer, nullable: true } preview_b64: { type: string, nullable: true } active_nodes: { type: integer } message: { type: string, nullable: true } Snapshot: type: object properties: id: { type: string, format: uuid } saved_project_id: { type: string, format: uuid } scene_key: { type: string } frame_number: { type: integer } status: { type: string } image_url: { type: string, nullable: true } thumbnail_url: { type: string, nullable: true } width: { type: integer, nullable: true } height: { type: integer, nullable: true } duration_ms: { type: integer, nullable: true } expires_at: { type: string, format: date-time } requested_at: { type: string, format: date-time } completed_at: { type: string, format: date-time, nullable: true } Export: type: object properties: id: { type: string, format: uuid } saved_project_id: { type: string, format: uuid } path: { type: string } image: { type: string } size_bytes: { type: integer, format: int64 } duration_sec: { type: number } width: { type: integer } height: { type: integer } file_extension: { type: string } file_type: { type: string } render_quality: { type: string } create_type: { type: string } produce_date: { type: string, format: date-time } auto_delete_date: { type: string, format: date-time } ExportDetail: allOf: - $ref: '#/components/schemas/Export' - type: object properties: files: type: array items: type: object properties: id: { type: string, format: uuid } thumbnail: { type: string } path: { type: string } size_bytes: { type: integer, format: int64 } file_type: { type: string } RenderNode: type: object properties: id: { type: string, format: uuid } name: { type: string } region: { type: string } node_ip: { type: string } worker_port: { type: integer } status: { type: string } node_kind: { type: string } owner_user_id: { type: string, format: uuid, nullable: true } current_ae_version: { type: string } node_agent_version: { type: string } ram_gb: { type: integer } cpu_cores: { type: integer } priority: { type: integer } is_active: { type: boolean } accepts_new_jobs: { type: boolean } last_heartbeat_at: { type: string, format: date-time } current_job_id: { type: string, format: uuid, nullable: true } last_cpu_pct: { type: integer } last_ram_available_mb: { type: integer } ae_running: { type: boolean } cache_used_gb: { type: integer } cached_template_count: { type: integer } lifetime_task_count: { type: integer, format: int64 } lifetime_crash_count: { type: integer } consecutive_failures: { type: integer } RenderNodeCreate: type: object required: [name, region, node_ip, worker_port, current_ae_version] properties: name: { type: string } region: { type: string } node_ip: { type: string } worker_port: { type: integer } current_ae_version: { type: string } ram_gb: { type: integer } cpu_cores: { type: integer } node_kind: { type: string } owner_user_id: { type: string, format: uuid } priority: { type: integer } RenderNodeDetail: allOf: - $ref: '#/components/schemas/RenderNode' - type: object properties: available_ae_versions: { type: array, items: { type: string } } cached_template_md5s: { type: array, items: { type: string } } last_maintenance_at: { type: string, format: date-time } next_maintenance_at: { type: string, format: date-time, nullable: true } NodeHealth: type: object properties: node_id: { type: string, format: uuid } recorded_at: { type: string, format: date-time } status: { type: string } cpu_pct: { type: integer } ram_available_mb: { type: integer } ae_running: { type: boolean } current_job_id: { type: string, format: uuid, nullable: true } current_frame: { type: integer, nullable: true } cache_used_gb: { type: integer } NodeHeartbeat: allOf: - $ref: '#/components/schemas/NodeHealth' NodeCrash: type: object properties: id: { type: string, format: uuid } node_id: { type: string, format: uuid } render_job_id: { type: string, format: uuid, nullable: true } crashed_at: { type: string, format: date-time } last_known_frame: { type: integer, nullable: true } crash_signal: { type: string } error_log: { type: string } log_file_url: { type: string } auto_recovered: { type: boolean } recovery_action: { type: string } recovered_at: { type: string, format: date-time, nullable: true } NodeUpdate: type: object properties: id: { type: string, format: uuid } update_file_name: { type: string } update_number: { type: integer } description: { type: string } target_ae_version: { type: string } in_update_queue: { type: boolean } rolled_out_to_node_ids: { type: array, items: { type: string, format: uuid } } create_date: { type: string, format: date-time }