openapi: 3.0.3 info: title: FlatRender Studio Service (internal) version: 1.0.0 description: | User's saved projects (the editor's state). Includes voiceover + audio mix settings. servers: - url: http://studio-svc.internal/v1 security: - BearerAuth: [] - ServiceToken: [] tags: - name: SavedProjects - name: SavedScenes - name: Audio - name: Internal paths: /saved-projects: get: tags: [SavedProjects] summary: List user's saved projects parameters: - { name: q, in: query, schema: { type: string } } - { name: type, in: query, schema: { type: string, enum: [Draft, Active, Archived, Trash] } } - { 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/SavedProjectSummary' } } meta: { $ref: '#/components/schemas/PaginationMeta' } post: tags: [SavedProjects] summary: Create new saved project from a template requestBody: required: true content: application/json: schema: type: object required: [original_project_id] properties: original_project_id: { type: string, format: uuid } name: { type: string } preset_story_id: { type: string, format: uuid } copy_default_values: { type: boolean, default: true } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/SavedProject' } /saved-projects/{id}: get: tags: [SavedProjects] parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SavedProjectFull' } patch: tags: [SavedProjects] summary: Update top-level fields (name, audio, etc.) parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/SavedProjectUpdate' } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SavedProject' } delete: tags: [SavedProjects] summary: Soft-delete (move to trash) parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } responses: '204': { description: Trashed } /saved-projects/{id}/restore: post: tags: [SavedProjects] summary: Restore from trash parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SavedProject' } /saved-projects/{id}/duplicate: post: tags: [SavedProjects] parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: content: application/json: schema: type: object properties: new_name: { type: string } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/SavedProject' } /saved-projects/{id}/autosave: put: tags: [SavedProjects] summary: Autosave entire project graph (debounced from UI) parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/SavedProjectFull' } responses: '200': content: application/json: schema: type: object properties: saved_at: { type: string, format: date-time } version: { type: integer } # ===================== AUDIO (NEW) ===================== /saved-projects/{id}/audio: get: tags: [Audio] parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/AudioSettings' } put: tags: [Audio] summary: Update audio mix parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/AudioSettings' } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/AudioSettings' } /saved-projects/{id}/voiceover: post: tags: [Audio] summary: | Upload or record voiceover. Returns target file_id. Use file service upload endpoints for actual binary; this attaches an existing file to the project. parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object required: [file_id] properties: file_id: { type: string, format: uuid, description: existing user_file_id } recorded_in_browser: { type: boolean, default: false } volume: { type: number, minimum: 0, maximum: 1, default: 1.0 } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/AudioSettings' } delete: tags: [Audio] summary: Remove voiceover parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } responses: '204': { description: Removed } /saved-projects/{id}/music: put: tags: [Audio] summary: Set music track (from library or upload) parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object properties: music_track_id: { type: string, format: uuid } music_file_id: { type: string, format: uuid } volume: { type: number, minimum: 0, maximum: 1 } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/AudioSettings' } # ===================== SCENES ===================== /saved-projects/{id}/scenes: get: tags: [SavedScenes] parameters: - { name: 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/SavedSceneFull' } } post: tags: [SavedScenes] summary: Add a scene from project template parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object required: [original_scene_id] properties: original_scene_id: { type: string, format: uuid } sort: { type: integer } scene_length_sec: { type: number } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/SavedSceneFull' } /saved-scenes/{scene_id}: patch: tags: [SavedScenes] parameters: - { name: scene_id, in: path, required: true, schema: { type: integer, format: int64 } } requestBody: required: true content: application/json: schema: type: object properties: scene_length_sec: { type: number } manual_color_selection: { type: boolean } selected_color_preset_id: { type: integer, format: int64 } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SavedScene' } delete: tags: [SavedScenes] parameters: - { name: scene_id, in: path, required: true, schema: { type: integer, format: int64 } } responses: '204': { description: Removed } /saved-projects/{id}/scenes/reorder: post: tags: [SavedScenes] parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object required: [ordered_ids] properties: ordered_ids: type: array items: { type: integer, format: int64 } responses: '204': { description: Reordered } /saved-scenes/{scene_id}/contents: put: tags: [SavedScenes] summary: Bulk-update contents for a scene parameters: - { name: scene_id, in: path, required: true, schema: { type: integer, format: int64 } } requestBody: required: true content: application/json: schema: type: object properties: contents: type: array items: { $ref: '#/components/schemas/SavedSceneContent' } responses: '200': { description: Updated } /saved-scenes/{scene_id}/colors: put: tags: [SavedScenes] summary: Bulk-update colors for a scene parameters: - { name: scene_id, in: path, required: true, schema: { type: integer, format: int64 } } requestBody: required: true content: application/json: schema: type: object properties: colors: type: array items: { $ref: '#/components/schemas/SavedSceneColor' } responses: '200': { description: Updated } /saved-projects/{id}/shared-colors: put: tags: [SavedProjects] summary: Bulk update project-level shared colors parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object properties: colors: type: array items: { $ref: '#/components/schemas/SavedSharedColor' } responses: '200': { description: Updated } /saved-projects/{id}/shared-layers: put: tags: [SavedProjects] summary: Bulk update project-level shared layers parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object properties: layers: type: array items: { $ref: '#/components/schemas/SavedSharedLayer' } responses: '200': { description: Updated } # ===================== INTERNAL ===================== /internal/saved-projects/{id}/snapshot-for-render: get: tags: [Internal] summary: | Called by Render Orchestrator to get the full JSX-ready payload. Returns everything needed to generate JSX (FIX/FLEXIBLE/Mockup/MV). security: [ServiceToken: []] parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SavedProjectSnapshot' } components: securitySchemes: BearerAuth: { type: http, scheme: bearer, bearerFormat: JWT } ServiceToken: { type: http, scheme: bearer } schemas: PaginationMeta: type: object properties: page: { type: integer } page_size: { type: integer } total: { type: integer } has_more: { type: boolean } SavedProjectSummary: type: object properties: id: { type: string, format: uuid } name: { type: string } image: { type: string } type: { type: string } original_project_id: { type: string, format: uuid } original_project_name: { type: string } original_container_slug: { type: string } choose_mode: { type: string } resolution: { type: string } project_duration_sec: { type: number } scene_count: { type: integer } last_edit_date: { type: string, format: date-time } created_at: { type: string, format: date-time } SavedProject: allOf: - $ref: '#/components/schemas/SavedProjectSummary' - type: object properties: frame_rate: { type: integer } vip_factor: { type: number } manual_color_picker: { type: boolean } selected_preset_story_id: { type: string, format: uuid, nullable: true } audio: { $ref: '#/components/schemas/AudioSettings' } SavedProjectUpdate: type: object properties: name: { type: string } type: { type: string, enum: [Draft, Active, Archived, Trash] } manual_color_picker: { type: boolean } selected_preset_story_id: { type: string, format: uuid, nullable: true } last_edit_step: { type: string } SavedProjectFull: allOf: - $ref: '#/components/schemas/SavedProject' - type: object properties: scenes: { type: array, items: { $ref: '#/components/schemas/SavedSceneFull' } } shared_colors: { type: array, items: { $ref: '#/components/schemas/SavedSharedColor' } } shared_color_presets: { type: array, items: { $ref: '#/components/schemas/SavedSharedColorPreset' } } shared_layers: { type: array, items: { $ref: '#/components/schemas/SavedSharedLayer' } } AudioSettings: type: object properties: music_track_id: { type: string, format: uuid, nullable: true } music_file_id: { type: string, format: uuid, nullable: true } music_url: { type: string, nullable: true } music_duration_sec: { type: number, nullable: true } music_volume: { type: number, minimum: 0, maximum: 1 } voiceover_file_id: { type: string, format: uuid, nullable: true } voiceover_url: { type: string, nullable: true } voiceover_duration_sec: { type: number, nullable: true } voiceover_volume: { type: number, minimum: 0, maximum: 1 } voiceover_recorded_in_browser: { type: boolean } sfx_enabled: { type: boolean } sfx_volume: { type: number, minimum: 0, maximum: 1 } SavedScene: type: object properties: id: { type: integer, format: int64 } saved_project_id: { type: string, format: uuid } original_scene_id: { type: string, format: uuid } key: { type: string } title: { type: string } image: { type: string } demo: { type: string } scene_type: { type: string } sort: { type: integer } scene_length_sec: { type: number } min_duration_sec: { type: number } max_duration_sec: { type: number } overlap_at_end_sec: { type: number } manual_color_selection: { type: boolean } SavedSceneFull: allOf: - $ref: '#/components/schemas/SavedScene' - type: object properties: contents: { type: array, items: { $ref: '#/components/schemas/SavedSceneContent' } } colors: { type: array, items: { $ref: '#/components/schemas/SavedSceneColor' } } color_presets: { type: array, items: { $ref: '#/components/schemas/SavedSceneColorPreset' } } characters: { type: array, items: { $ref: '#/components/schemas/SavedSceneCharacter' } } SavedSceneContent: type: object properties: id: { type: integer, format: int64 } key: { type: string } type: { type: string } value: { type: string } value_file_id: { type: string, format: uuid, nullable: true } file_url_cached: { type: string, nullable: true } inserted_file_type: { type: string, nullable: true } font_face: { type: string, nullable: true } font_size: { type: integer, nullable: true } justify: { type: string } position_in_container: { type: integer } direction_layer_value: { type: integer } is_text_box: { type: boolean } ai_input_type: { type: string, nullable: true } selected_dp: { type: integer, nullable: true } repeater_item_key: { type: string, nullable: true } repeater_index: { type: integer, nullable: true } sort: { type: integer } SavedSceneColor: type: object properties: id: { type: integer, format: int64 } element_key: { type: string } title: { type: string } icon: { type: string } attr_value: { type: string } value: { type: string } is_selected: { type: boolean } sort: { type: integer } SavedSceneColorPreset: type: object properties: id: { type: integer, format: int64 } is_selected: { type: boolean } sort: { type: integer } items: type: array items: type: object properties: element_key: { type: string } value: { type: string } sort: { type: integer } SavedSceneCharacter: type: object properties: id: { type: integer, format: int64 } key: { type: string, format: uuid } name: { type: string } icon: { type: string } controllers: type: array items: type: object properties: name: { type: string } key: { type: string } value: { type: string } sort: { type: integer } SavedSharedColor: type: object properties: id: { type: integer, format: int64 } element_key: { type: string } title: { type: string } attr_value: { type: string } value: { type: string } is_selected: { type: boolean } sort: { type: integer } SavedSharedColorPreset: type: object properties: id: { type: integer, format: int64 } name: { type: string } is_selected: { type: boolean } sort: { type: integer } items: type: array items: type: object properties: element_key: { type: string } value: { type: string } SavedSharedLayer: type: object properties: id: { type: integer, format: int64 } key: { type: string } title: { type: string } type: { type: string } value: { type: string } value_file_id: { type: string, format: uuid, nullable: true } file_url_cached: { type: string, nullable: true } font_face: { type: string } font_size: { type: integer } justify: { type: string } position_in_container: { type: integer } direction_layer_value: { type: integer } is_text_box: { type: boolean } sort: { type: integer } SavedProjectSnapshot: type: object description: | Complete payload for JSX generation. Same shape returned for any choose_mode; render service decides which JSX generator to use. properties: saved_project_id: { type: string, format: uuid } tenant_id: { type: string, format: uuid } user_id: { type: string, format: uuid } original_project_id: { type: string, format: uuid } original_project_name: { type: string } choose_mode: { type: string } resolution: { type: string } frame_rate: { type: integer } project_duration_sec: { type: number } vip_factor: { type: number } aep: type: object properties: url: { type: string } md5: { type: string } size_bytes: { type: integer, format: int64 } render_comp: { type: string } original_width: { type: integer } original_height: { type: integer } audio: { $ref: '#/components/schemas/AudioSettings' } shared_colors: type: array items: { $ref: '#/components/schemas/SavedSharedColor' } shared_layers: type: array items: { $ref: '#/components/schemas/SavedSharedLayer' } scenes: type: array items: { $ref: '#/components/schemas/SavedSceneFull' }