{
  "openapi": "3.1.0",
  "info": {
    "title": "Pedra API",
    "version": "1.0.0",
    "summary": "AI photo and video editing for real estate.",
    "description": "Programmatic access to Pedra's AI real estate image and video transformations — virtual staging, renovation, prompt-based editing, image enhancement, sky replacement, blurring, object removal, and listing-video generation.\n\n## Authentication\nEvery request is authenticated with an API key passed as the `apiKey` field in the JSON request body (not an HTTP header). Get a key by signing up at https://app.pedra.ai and opening Settings → API.\n\n## Conventions\n- All endpoints are `POST` with a JSON body and return JSON (`Content-Type: application/json`).\n- Image inputs (`imageUrl`, `maskUrl`, etc.) accept either a publicly accessible HTTPS URL or a base64 data URI (`data:image/png;base64,...`). Pedra stops downloading a remote URL after ~20s, so prefer base64 for auth-walled or short-lived presigned URLs.\n- All calls are **synchronous** — there is no polling or job ID. Image endpoints return in ~10–30s; `create_video` blocks until the video is rendered (~2–10 min), so set a generous client timeout.\n- Errors are returned with a non-200 status and a JSON body of the shape `{ \"error\": \"...\" }`.\n\nSee the full docs at https://pedra.ai/api-documentation, the SDKs at https://pedra.ai/api-documentation/sdks, and the MCP server at https://pedra.ai/api-documentation/mcp.",
    "contact": {
      "name": "Pedra API support",
      "url": "https://pedra.ai/api-documentation",
      "email": "felix@pedra.ai"
    },
    "license": {
      "name": "Proprietary — see Terms",
      "url": "https://pedra.ai/legal"
    },
    "termsOfService": "https://pedra.ai/legal"
  },
  "security": [],
  "servers": [
    {
      "url": "https://app.pedra.ai",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Pedra API documentation",
    "url": "https://pedra.ai/api-documentation"
  },
  "tags": [
    { "name": "Image enhancement", "description": "Enhance lighting/color, correct perspective, replace skies, and blur sensitive content." },
    { "name": "Virtual staging", "description": "Furnish, empty, renovate, prompt-edit, and remove objects from room photos." },
    { "name": "Video", "description": "Generate listing videos from photos." },
    { "name": "Account", "description": "Account-level reads and feedback." }
  ],
  "paths": {
    "/api/enhance": {
      "post": {
        "tags": ["Image enhancement"],
        "summary": "Auto-enhance a real estate photo",
        "description": "Improves lighting, color, and contrast on a listing photo. Costs 1 credit. Responds in ~10s.",
        "operationId": "enhance",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "imageUrl"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
                  "preserveOriginalFraming": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, keeps the original crop/framing instead of recomposing the shot."
                  },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://example.com/listing-photo.jpg",
                "preserveOriginalFraming": true
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ImageArrayResult" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/enhance_and_correct_perspective": {
      "post": {
        "tags": ["Image enhancement"],
        "summary": "Enhance a photo and straighten vertical lines",
        "description": "Auto-enhances the photo and corrects perspective so vertical lines are straight. Costs 1 credit. Responds in ~15s.",
        "operationId": "enhanceAndCorrectPerspective",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "imageUrl"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
                  "preserveOriginalFraming": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, keeps the original crop/framing instead of recomposing the shot."
                  },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://example.com/listing-photo.jpg"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ImageArrayResult" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/sky_blue": {
      "post": {
        "tags": ["Image enhancement"],
        "summary": "Replace the sky in an exterior photo",
        "description": "Swaps a dull or overcast sky for a chosen style. Costs 1 credit. Responds in ~10s.",
        "operationId": "skyBlue",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "imageUrl"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
                  "skyStyle": {
                    "type": "string",
                    "default": "sunny",
                    "enum": ["sunny", "sunny-no-reflections", "sunrise", "dawn", "night"],
                    "description": "Target sky look."
                  },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://example.com/house-exterior.jpg",
                "skyStyle": "sunny"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ImageObjectResult" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/blur": {
      "post": {
        "tags": ["Image enhancement"],
        "summary": "Blur sensitive content in a photo",
        "description": "Blurs faces, license plates, logos, or other sensitive content. Costs 1 credit. Responds in ~10s.",
        "operationId": "blur",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "imageUrl", "objectsToBlur"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
                  "objectsToBlur": {
                    "type": "string",
                    "description": "Comma-separated list of what to blur, e.g. \"faces\", \"license plates\", \"logos\", or \"faces, license plates\".",
                    "examples": ["faces, license plates"]
                  },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://example.com/street-view.jpg",
                "objectsToBlur": "faces, license plates"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ImageObjectResult" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/empty_room": {
      "post": {
        "tags": ["Virtual staging"],
        "summary": "Remove all furniture from a room",
        "description": "Empties a furnished room, returning a clean empty space. Costs 1 credit. Responds in ~30s.",
        "operationId": "emptyRoom",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "imageUrl"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://example.com/furnished-room.jpg"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ImageArrayResult" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/furnish": {
      "post": {
        "tags": ["Virtual staging"],
        "summary": "Furnish an empty room",
        "description": "Adds furniture and decor to an empty room in a chosen style. Costs 1 credit at High creativity, 2 at Medium. Responds in ~30s.",
        "operationId": "furnish",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "imageUrl", "roomType", "style"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
                  "roomType": { "$ref": "#/components/schemas/RoomType" },
                  "style": { "$ref": "#/components/schemas/Style" },
                  "creativity": { "$ref": "#/components/schemas/Creativity" },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://example.com/empty-bedroom.jpg",
                "roomType": "Bedroom",
                "style": "Minimalist",
                "creativity": "Medium"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ImageArrayResult" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/renovation": {
      "post": {
        "tags": ["Virtual staging"],
        "summary": "Renovate a room in a new style",
        "description": "Re-renovates an existing room in a chosen style, optionally re-furnishing it in the same call. Costs 1 credit at High creativity, 2 at Medium. Responds in ~30s.",
        "operationId": "renovation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "imageUrl", "style"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
                  "style": { "$ref": "#/components/schemas/Style" },
                  "preserveWindows": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, keeps windows and their views unchanged."
                  },
                  "furnish": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, also furnishes the renovated room in the same call."
                  },
                  "roomType": {
                    "type": "string",
                    "default": "Auto",
                    "description": "Used when `furnish` is true. \"Auto\" lets Pedra detect the room type. Otherwise a predefined room type (\"Living room\", \"Dining + Living room\", \"Bedroom\", \"Dining room\", \"Terrace\", \"Entrance\", \"Office\", \"Bathroom\") or any custom string."
                  },
                  "creativity": { "$ref": "#/components/schemas/Creativity" },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://example.com/dated-kitchen.jpg",
                "style": "Modern",
                "preserveWindows": true,
                "furnish": false,
                "creativity": "Medium"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ImageArrayResult" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/edit_via_prompt": {
      "post": {
        "tags": ["Virtual staging"],
        "summary": "Edit a photo with a text prompt",
        "description": "Applies a plain-English editing instruction to a photo. Costs 2 credits. Responds in ~15s.",
        "operationId": "editViaPrompt",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "imageUrl", "prompt"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
                  "prompt": {
                    "type": "string",
                    "description": "Plain-English instruction for the edit.",
                    "examples": ["Add a modern chandelier to the ceiling"]
                  },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://example.com/living-room.jpg",
                "prompt": "Repaint the walls white and remove the rug"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ImageObjectResult" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/remove_object": {
      "post": {
        "tags": ["Virtual staging"],
        "summary": "Remove objects using a mask",
        "description": "Removes the masked region from a photo and inpaints the background. Costs 1 credit. Responds in ~15s.",
        "operationId": "removeObject",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "imageUrl", "maskUrl"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
                  "maskUrl": {
                    "allOf": [{ "$ref": "#/components/schemas/ImageInput" }],
                    "description": "Mask image where white pixels mark the area to remove and black pixels are preserved."
                  },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://example.com/room.jpg",
                "maskUrl": "https://example.com/room-mask.png"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ImageObjectResult" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/create_video": {
      "post": {
        "tags": ["Video"],
        "summary": "Generate a listing video from photos",
        "description": "Renders a listing video from an ordered list of frames, with per-frame effects, subtitles, music, voiceover, property characteristics, and branding. Costs 5 credits per animated frame (0 for static frames). Synchronous — blocks until the video is rendered (~2–10 min), so set a generous client timeout.",
        "operationId": "createVideo",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "images"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "images": {
                    "type": "array",
                    "minItems": 1,
                    "description": "Ordered list of frames that make up the video.",
                    "items": { "$ref": "#/components/schemas/VideoFrame" }
                  },
                  "isVertical": {
                    "type": "boolean",
                    "default": false,
                    "description": "Render 9:16 vertical (Reels/TikTok) instead of horizontal."
                  },
                  "propertyCharacteristics": {
                    "type": "array",
                    "description": "Property details shown on frames whose `characteristics.enabled` is true. Built-in labels render with an icon: Bedrooms, Bathrooms, Surface, Price, Location, Parking, Heating, Outdoor. Custom labels render without an icon.",
                    "items": { "$ref": "#/components/schemas/PropertyCharacteristic" }
                  },
                  "music": { "$ref": "#/components/schemas/Music" },
                  "voice": { "$ref": "#/components/schemas/Voice" },
                  "branding": { "$ref": "#/components/schemas/Branding" },
                  "endingTitle": {
                    "type": "string",
                    "description": "Headline shown on the final card."
                  },
                  "endingSubtitle": {
                    "type": "string",
                    "description": "Subtitle shown on the final card, e.g. a CTA or contact details."
                  },
                  "propertyId": { "$ref": "#/components/schemas/PropertyId" }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "images": [
                  { "imageUrl": "https://example.com/exterior.jpg", "effect": "zoom-in", "subtitle": "Renovated 2-bed in Barcelona", "characteristics": { "enabled": true } },
                  { "imageUrl": "https://example.com/living-room.jpg", "effect": "zoom-out", "subtitle": "Bright open-plan living space" },
                  { "imageUrl": "https://example.com/before.jpg", "secondImageUrl": "https://example.com/after.jpg", "effect": "transition", "title": "Before & After" }
                ],
                "isVertical": false,
                "propertyCharacteristics": [
                  { "label": "Price", "value": "€450,000" },
                  { "label": "Bedrooms", "value": "2" },
                  { "label": "Surface", "value": "85 m²" }
                ],
                "music": { "enabled": true, "track": "cinematic" },
                "endingTitle": "Book a viewing",
                "endingSubtitle": "felix@pedra.ai"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Video rendered.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/VideoResult" },
                "example": {
                  "message": "Video created successfully",
                  "videoId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "videoUrl": "https://img.pedra.ai/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/update_video": {
      "post": {
        "tags": ["Video"],
        "summary": "Edit an existing video without re-rendering unchanged clips",
        "description": "Edits a video (by videoId) created with create_video. Clips whose source photo + effect (+ second photo for transitions) are unchanged reuse their already-rendered clip — no re-animation, no credits. Only new or changed non-static frames re-animate at 5 credits each. Omit `images` to change only audio/text/branding (a free re-stitch). Omitted music/voice/branding/ending fields keep their stored values. Synchronous — blocks until the new video is rendered. If the video was created with a `propertyId`, that link is inherited automatically — no need to pass it again.",
        "operationId": "updateVideo",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "videoId"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "videoId": { "type": "string", "description": "Id of the video to edit (the videoId returned by create_video)." },
                  "images": {
                    "type": "array",
                    "description": "Full ordered frame list to rebuild the timeline; matching photo+effect clips are reused. Omit to keep the current timeline and edit only audio/text/branding.",
                    "items": { "$ref": "#/components/schemas/VideoFrame" }
                  },
                  "isVertical": { "type": "boolean", "description": "Render 9:16 vertical instead of horizontal (only applied when `images` is sent)." },
                  "propertyCharacteristics": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/PropertyCharacteristic" }
                  },
                  "music": { "$ref": "#/components/schemas/Music" },
                  "voice": { "$ref": "#/components/schemas/Voice" },
                  "branding": { "$ref": "#/components/schemas/Branding" },
                  "endingTitle": { "type": "string", "description": "Headline shown on the final card." },
                  "endingSubtitle": { "type": "string", "description": "Subtitle shown on the final card." }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "videoId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                "music": { "enabled": true, "track": "upbeat" }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Video re-rendered.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/VideoResult" },
                "example": {
                  "message": "Video updated successfully",
                  "videoId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                  "videoUrl": "https://img.pedra.ai/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/generate_voice_script": {
      "post": {
        "tags": ["Video"],
        "summary": "Generate a voiceover script from photos",
        "description": "Writes a short, natural voiceover script from listing photos and optional property facts, using vision so the script reflects what's in the images. Returns the script text — pass it to /api/generate_voice. Free.",
        "operationId": "generateVoiceScript",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "images": {
                    "type": "array",
                    "description": "Photos to base the script on: URL strings or { imageUrl } objects.",
                    "items": { "type": "string" }
                  },
                  "propertyCharacteristics": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/PropertyCharacteristic" }
                  },
                  "language": { "type": "string", "default": "English", "description": "Script language. See /api/music_library for accepted values." }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "images": ["https://example.com/exterior.jpg", "https://example.com/living-room.jpg"],
                "propertyCharacteristics": [{ "label": "Bedrooms", "value": "2" }],
                "language": "English"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Script generated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": { "type": "string", "examples": ["Script generated successfully"] },
                    "script": { "type": "string", "description": "The generated voiceover script." }
                  }
                },
                "example": {
                  "message": "Script generated successfully",
                  "script": "Welcome to this bright, renovated two-bedroom apartment."
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/generate_voice": {
      "post": {
        "tags": ["Video"],
        "summary": "Render a voiceover from a script",
        "description": "Renders a script to a voiceover audio track via text-to-speech, returning an audioId to pass to a video's voice.audioId (which also drives synced subtitles). Costs 1 credit. Text limit is 1000 characters.",
        "operationId": "generateVoice",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "text"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "text": { "type": "string", "maxLength": 1000, "description": "The script to narrate." },
                  "language": { "type": "string", "default": "English", "description": "Voice language (selects the speaker). See /api/music_library for accepted values." }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "text": "Welcome to this bright, renovated two-bedroom apartment.",
                "language": "English"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Voiceover rendered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": { "type": "string", "examples": ["Voice generated successfully"] },
                    "audioId": { "type": "string", "description": "Pass to a video's voice.audioId to attach the narration." },
                    "audioUrl": { "type": "string", "description": "Public URL of the rendered mp3." },
                    "alignmentUrl": { "type": "string", "description": "URL of the word-alignment JSON used for synced subtitles." },
                    "duration": { "type": "integer", "description": "Approximate duration in seconds." }
                  }
                },
                "example": {
                  "message": "Voice generated successfully",
                  "audioId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
                  "audioUrl": "https://img.pedra.ai/audio/b2c3d4e5-f6a7-8901-bcde-f23456789012.mp3",
                  "alignmentUrl": "https://img.pedra.ai/audio/b2c3d4e5-f6a7-8901-bcde-f23456789012.alignment.json",
                  "duration": 7
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/InsufficientCredits" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/music_library": {
      "get": {
        "tags": ["Video"],
        "summary": "List the background-music catalog",
        "description": "Returns the valid `music.track` values (genre keys) with display labels, plus the languages accepted by the voiceover endpoints. Read-only — never deducts credits. Also available as POST.",
        "operationId": "musicLibrary",
        "responses": {
          "200": {
            "description": "Music catalog and voice languages.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tracks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "track": { "type": "string", "description": "Genre key for music.track." },
                          "label": { "type": "string", "description": "Display label." }
                        }
                      }
                    },
                    "variantsPerTrack": { "type": "integer", "description": "Number of shuffled variants per genre." },
                    "defaultTrack": { "type": "string", "description": "Default genre key." },
                    "voiceLanguages": { "type": "array", "items": { "type": "string" }, "description": "Languages accepted by generate_voice / generate_voice_script." }
                  }
                },
                "example": {
                  "tracks": [
                    { "track": "acoustic", "label": "Acoustic & Warm" },
                    { "track": "chill", "label": "Chill Beats" },
                    { "track": "cinematic", "label": "Cinematic & Orchestral" },
                    { "track": "electronic", "label": "Modern Electronic" },
                    { "track": "upbeat", "label": "Upbeat & Energetic" }
                  ],
                  "variantsPerTrack": 6,
                  "defaultTrack": "chill",
                  "voiceLanguages": ["English", "Español", "Français", "Deutsch", "Português", "Italiano", "Nederlands", "Polski"]
                }
              }
            }
          },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/list_properties": {
      "post": {
        "tags": ["Properties"],
        "summary": "List the account's properties",
        "description": "Returns the account's properties (newest first), each with a photo count and an appUrl that opens it in the Pedra web app.",
        "operationId": "listProperties",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey"],
                "properties": { "apiKey": { "$ref": "#/components/schemas/ApiKey" } }
              },
              "example": { "apiKey": "YOUR_API_KEY" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account's properties.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "properties": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "propertyId": { "type": "string" },
                          "name": { "type": "string" },
                          "createdAt": { "type": "string" },
                          "photoCount": { "type": "integer" },
                          "appUrl": { "type": "string" }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "properties": [
                    { "propertyId": "a1b2c3d4", "name": "Calle Mayor 12", "createdAt": "2026-06-30T10:00:00.000Z", "photoCount": 8, "appUrl": "https://app.pedra.ai/?projectId=a1b2c3d4" }
                  ]
                }
              }
            }
          },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/list_property_images": {
      "post": {
        "tags": ["Properties"],
        "summary": "List a property's photos",
        "description": "Returns a property's photos as public img.pedra.ai URLs — ready to pass to /api/create_video or the image-editing endpoints.",
        "operationId": "listPropertyImages",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "propertyId"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "propertyId": { "type": "string", "description": "The property's id (from /api/list_properties)." }
                }
              },
              "example": { "apiKey": "YOUR_API_KEY", "propertyId": "a1b2c3d4" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The property's photos.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "propertyId": { "type": "string" },
                    "name": { "type": "string" },
                    "images": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "imageId": { "type": "string" },
                          "url": { "type": "string" },
                          "name": { "type": "string" },
                          "aspectRatio": { "type": "number" }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "propertyId": "a1b2c3d4",
                  "name": "Calle Mayor 12",
                  "images": [{ "imageId": "e5f6", "url": "https://img.pedra.ai/e5f6", "name": "Image 1", "aspectRatio": 1.5 }]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/create_property": {
      "post": {
        "tags": ["Properties"],
        "summary": "Create a property",
        "description": "Creates an empty property and returns its propertyId and an appUrl (open it in Pedra to upload brand-new local photos).",
        "operationId": "createProperty",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "name": { "type": "string", "description": "Property name, e.g. the listing address." }
                }
              },
              "example": { "apiKey": "YOUR_API_KEY", "name": "Calle Mayor 12" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Property created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": { "type": "string" },
                    "propertyId": { "type": "string" },
                    "appUrl": { "type": "string" }
                  }
                },
                "example": { "message": "Property created", "propertyId": "a1b2c3d4", "appUrl": "https://app.pedra.ai/?projectId=a1b2c3d4" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/add_images_to_property": {
      "post": {
        "tags": ["Properties"],
        "summary": "Add photos to a property by URL",
        "description": "Adds photos to a property by URL — the server fetches each URL and stores it (any public https image, or a small data: URI), then returns the stored img.pedra.ai URLs. Up to 20 per call.",
        "operationId": "addImagesToProperty",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "propertyId", "imageUrls"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "propertyId": { "type": "string" },
                  "imageUrls": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Up to 20 image URLs to fetch and add."
                  }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "propertyId": "a1b2c3d4",
                "imageUrls": ["https://example.com/kitchen.jpg", "https://example.com/living-room.jpg"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Photos added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": { "type": "string" },
                    "propertyId": { "type": "string" },
                    "added": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "imageId": { "type": "string" },
                          "url": { "type": "string" },
                          "aspectRatio": { "type": "number" }
                        }
                      }
                    },
                    "failed": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": { "url": { "type": "string" }, "error": { "type": "string" } }
                      }
                    },
                    "appUrl": { "type": "string" }
                  }
                },
                "example": {
                  "message": "Added 2 image(s)",
                  "propertyId": "a1b2c3d4",
                  "added": [{ "imageId": "e5f6", "url": "https://img.pedra.ai/e5f6", "aspectRatio": 1.5 }],
                  "failed": [],
                  "appUrl": "https://app.pedra.ai/?projectId=a1b2c3d4"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/credits": {
      "post": {
        "tags": ["Account"],
        "summary": "Check remaining credits",
        "description": "Returns the account plan and remaining credit balance. Read-only — never deducts credits. Also available as `GET /api/credits?apiKey=YOUR_API_KEY`.",
        "operationId": "credits",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey"],
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" }
                }
              },
              "example": { "apiKey": "YOUR_API_KEY" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current plan and credit balance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plan": { "type": "string", "description": "Account plan.", "examples": ["pro"] },
                    "creditsRemaining": { "type": "integer", "description": "Credits available for generations." }
                  }
                },
                "example": { "plan": "pro", "creditsRemaining": 4820 }
              }
            }
          },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/feedback": {
      "post": {
        "tags": ["Account"],
        "summary": "Vote on a generated image and optionally request credit back",
        "description": "Submits a thumbs up/down on a previously generated image and, on a down vote, can request a credit refund for eligible generations. Free — voting never costs credits. Credit back is eligible only for empty_room, remove_object, edit_via_prompt, renovation, and (non-high-creativity) furnish generations requested within 2 hours.",
        "operationId": "feedback",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["apiKey", "vote"],
                "description": "Provide either `imageUrl` or `imageId` to identify the generation.",
                "properties": {
                  "apiKey": { "$ref": "#/components/schemas/ApiKey" },
                  "imageUrl": {
                    "type": "string",
                    "description": "URL of the generated image (as returned by a generation endpoint). Provide this or `imageId`."
                  },
                  "imageId": {
                    "type": "string",
                    "description": "Bare id of the generated image. Provide this or `imageUrl`."
                  },
                  "vote": {
                    "type": "string",
                    "enum": ["up", "down", ""],
                    "description": "\"up\" or \"down\". An empty string clears a previous vote."
                  },
                  "comment": {
                    "type": "string",
                    "description": "Optional free-text explanation of the vote."
                  },
                  "creditBack": {
                    "type": "boolean",
                    "default": false,
                    "description": "On a down vote, request a credit refund for the generation if eligible."
                  }
                }
              },
              "example": {
                "apiKey": "YOUR_API_KEY",
                "imageUrl": "https://img.pedra.ai/a1b2c3d4",
                "vote": "down",
                "comment": "Furniture looked unrealistic",
                "creditBack": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vote recorded. Includes a `creditBack` object only when a refund was requested.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FeedbackResult" },
                "examples": {
                  "simpleVote": {
                    "summary": "Vote without credit back",
                    "value": { "ok": true, "vote": "negative" }
                  },
                  "withCreditBack": {
                    "summary": "Down vote with approved credit back",
                    "value": { "ok": true, "vote": "negative", "creditBack": { "status": "approved", "creditsRefunded": 2, "reason": "Eligible generation within the refund window." } }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/InvalidApiKey" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ApiKey": {
        "type": "string",
        "description": "Account API key, sent in the request body. Get one at https://app.pedra.ai under Settings → API."
      },
      "ImageInput": {
        "type": "string",
        "description": "A publicly accessible HTTPS image URL or a base64 data URI (`data:image/png;base64,...`). Remote URLs are fetched within ~20s; use base64 for auth-walled or short-lived presigned URLs.",
        "examples": ["https://example.com/photo.jpg"]
      },
      "PropertyId": {
        "type": "string",
        "description": "Optional id of the property (from /api/list_properties or /api/create_property) this photo/video belongs to. When set, the result is saved into that property's gallery (or, for create_video, its Videos tab) — visible and editable from the app — instead of only being returned as a URL."
      },
      "RoomType": {
        "type": "string",
        "description": "Predefined room type (\"Living room\", \"Dining + Living room\", \"Bedroom\", \"Dining room\", \"Terrace\", \"Entrance\", \"Office\", \"Bathroom\") or any custom string (e.g. \"Kid's room\", \"Home gym\").",
        "examples": ["Bedroom"]
      },
      "Style": {
        "type": "string",
        "description": "A predefined style name (\"Minimalist\", \"Modern\", \"Scandinavian\", \"Industrial\", \"Bohemian\", \"Mid-century modern\", \"Traditional\", \"Mediterranean\", \"Coastal\", \"Rustic\", \"Farmhouse\", \"Contemporary\") or a public URL to a reference image to match.",
        "examples": ["Minimalist"]
      },
      "Creativity": {
        "type": "string",
        "default": "Medium",
        "enum": ["Medium", "High"],
        "description": "Generation creativity. Medium stays closer to the source (2 credits); High allows more variation (1 credit)."
      },
      "VideoFrame": {
        "type": "object",
        "required": ["imageUrl", "effect"],
        "properties": {
          "imageUrl": { "$ref": "#/components/schemas/ImageInput" },
          "effect": {
            "type": "string",
            "enum": ["zoom-in", "zoom-out", "transition", "static"],
            "description": "Frame animation. \"transition\" morphs into `secondImageUrl`; \"static\" is a still frame (0 credits)."
          },
          "secondImageUrl": {
            "allOf": [{ "$ref": "#/components/schemas/ImageInput" }],
            "description": "Target image for the \"transition\" effect. Required when `effect` is \"transition\"."
          },
          "subtitle": { "type": "string", "description": "Caption shown over the frame." },
          "title": { "type": "string", "description": "Large title overlay on the frame, e.g. \"Living Room\"." },
          "watermark": { "$ref": "#/components/schemas/Watermark" },
          "characteristics": {
            "type": "object",
            "description": "Per-frame toggle for the property-characteristics overlay.",
            "properties": {
              "enabled": { "type": "boolean", "description": "Show the `propertyCharacteristics` overlay on this frame." }
            }
          }
        }
      },
      "Watermark": {
        "type": "object",
        "description": "Per-frame watermark settings.",
        "properties": {
          "enabled": { "type": "boolean", "default": true, "description": "Show a watermark on this frame." },
          "position": {
            "type": "string",
            "default": "bottom-right",
            "enum": ["top-left", "top", "top-right", "left", "center", "right", "bottom-left", "bottom", "bottom-right"],
            "description": "Watermark placement."
          },
          "opacity": { "type": "number", "default": 1.0, "minimum": 0.0, "maximum": 1.0, "description": "Watermark opacity, 0.0–1.0." }
        }
      },
      "PropertyCharacteristic": {
        "type": "object",
        "required": ["label", "value"],
        "properties": {
          "label": { "type": "string", "description": "Characteristic name. Built-in labels (Bedrooms, Bathrooms, Surface, Price, Location, Parking, Heating, Outdoor) render with an icon.", "examples": ["Price"] },
          "value": { "type": "string", "examples": ["€450,000"] }
        }
      },
      "Music": {
        "type": "object",
        "required": ["enabled"],
        "description": "Background music settings.",
        "properties": {
          "enabled": { "type": "boolean" },
          "track": {
            "type": "string",
            "enum": ["acoustic", "chill", "cinematic", "electronic", "upbeat"],
            "description": "Music genre. One of the keys from GET /api/music_library. Required when `enabled` is true. Legacy values (calm, uplifting, corporate, piano) are still accepted."
          }
        }
      },
      "Voice": {
        "type": "object",
        "required": ["enabled"],
        "description": "Voiceover settings. Generate the audioId with POST /api/generate_voice.",
        "properties": {
          "enabled": { "type": "boolean" },
          "audioId": { "type": "string", "description": "Id of a voiceover from /api/generate_voice. Required when `enabled` is true. Attaches the narration and drives synced subtitles." },
          "audioUrl": { "type": "string", "description": "Legacy alias for audioId." },
          "showSubtitles": { "type": "boolean", "default": true, "description": "Burn in word-synced subtitles from the voiceover." }
        }
      },
      "Branding": {
        "type": "object",
        "description": "Agency/agent branding applied to the video.",
        "properties": {
          "showWatermark": { "type": "boolean", "description": "Use a custom logo as the watermark." },
          "watermarkUrl": { "type": "string", "description": "URL to the logo (PNG with transparency recommended)." },
          "showProfessionalPicture": { "type": "boolean", "description": "Show the agent photo on the ending card." },
          "professionalPictureUrl": { "type": "string", "description": "URL to the agent headshot." },
          "primaryColor": { "type": "string", "description": "Hex color for overlays. Defaults to white (#ffffff).", "default": "#ffffff", "examples": ["#007BFF"] }
        }
      },
      "ImageArrayResult": {
        "type": "object",
        "description": "Success response whose `output` is an array of result images.",
        "properties": {
          "message": { "type": "string", "examples": ["Success"] },
          "output": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": { "type": "string", "description": "URL of a generated image.", "examples": ["https://img.pedra.ai/generated-id"] }
              }
            }
          }
        }
      },
      "ImageObjectResult": {
        "type": "object",
        "description": "Success response whose `output` is a single result image.",
        "properties": {
          "message": { "type": "string", "examples": ["Success"] },
          "output": {
            "type": "object",
            "properties": {
              "url": { "type": "string", "description": "URL of the generated image.", "examples": ["https://img.pedra.ai/generated-id"] }
            }
          }
        }
      },
      "VideoResult": {
        "type": "object",
        "properties": {
          "message": { "type": "string", "examples": ["Video created successfully"] },
          "videoId": { "type": "string", "description": "Identifier of the rendered video." },
          "videoUrl": { "type": "string", "description": "URL of the rendered video." }
        }
      },
      "FeedbackResult": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "vote": {
            "type": ["string", "null"],
            "description": "Recorded vote: \"positive\", \"negative\", or null when cleared.",
            "examples": ["negative"]
          },
          "creditBack": {
            "type": "object",
            "description": "Present only when a credit refund was requested.",
            "properties": {
              "status": { "type": "string", "enum": ["approved", "rejected", "ineligible"] },
              "creditsRefunded": { "type": "integer" },
              "reason": { "type": "string" }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Human-readable error description." }
        }
      }
    },
    "responses": {
      "ImageArrayResult": {
        "description": "Success. `output` is an array of generated images.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ImageArrayResult" },
            "example": { "message": "Success", "output": [{ "url": "https://img.pedra.ai/generated-id" }] }
          }
        }
      },
      "ImageObjectResult": {
        "description": "Success. `output` is a single generated image.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ImageObjectResult" },
            "example": { "message": "Success", "output": { "url": "https://img.pedra.ai/generated-id" } }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid request — bad or unreachable image URL, missing required field, or validation error.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Invalid imageUrl" }
          }
        }
      },
      "InsufficientCredits": {
        "description": "Not enough credits for the requested generation.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Insufficient credits" }
          }
        }
      },
      "InvalidApiKey": {
        "description": "Missing, invalid, or revoked API key.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "User not found" }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "Wrong HTTP method — all endpoints accept POST (credits also accepts GET).",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Method not allowed" }
          }
        }
      }
    }
  }
}
