{
  "openapi": "3.0.0",
  "paths": {
    "/altcha": {
      "get": {
        "description": "Returns a new Altcha proof-of-work challenge to be solved client-side before form submission.",
        "operationId": "AltchaController_getChallenge",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Returns a new challenge"
          }
        },
        "summary": "Get Challenge",
        "tags": [
          "Altcha"
        ]
      }
    },
    "/form/{draftId}": {
      "post": {
        "description": "Submits a contact form associated with a draft domain. The formData field must be a JSON string where each key maps to { label: string, value: string }. A proof key containing a valid Altcha proof-of-work solution is required — obtain one via GET /altcha before submitting. The proof is validated server-side. Requires an active subscription on the draft.",
        "operationId": "FormController_submitForm",
        "parameters": [
          {
            "name": "draftId",
            "required": true,
            "in": "path",
            "description": "Draft ID",
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/SubmitFormDTO"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Form submitted and email delivered."
          },
          "400": {
            "description": "Missing or invalid Altcha proof, or file validation failed."
          },
          "403": {
            "description": "Subscription expired or inactive."
          },
          "404": {
            "description": "Draft not found or domain not configured."
          }
        },
        "summary": "Submit Form",
        "tags": [
          "Form"
        ]
      }
    },
    "/stores/{id}": {
      "post": {
        "description": "Create a Webround Commerce Store starting from a draft.",
        "operationId": "StoreController_createStore",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "The UUID of the draft",
            "schema": {}
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateStoreDTO"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Store created successfully"
          },
          "400": {
            "description": "Bad Request - Possible validation or business logic errors",
            "content": {
              "application/json": {
                "examples": {
                  "alreadyShop": {
                    "summary": "Draft is already a shop",
                    "value": {
                      "message": "This draft is already a shop",
                      "error": "Bad Request",
                      "statusCode": 400
                    }
                  },
                  "alreadyExists": {
                    "summary": "Store already exists",
                    "value": {
                      "message": "Store already exists",
                      "error": "Bad Request",
                      "statusCode": 400
                    }
                  },
                  "userCreation": {
                    "summary": "User creation failed",
                    "value": {
                      "message": "Error creating user",
                      "error": "Bad Request",
                      "statusCode": 400
                    }
                  },
                  "pagesFailed": {
                    "summary": "Pages generation failed",
                    "value": {
                      "message": "Failed to generate shop pages",
                      "error": "Bad Request",
                      "statusCode": 400
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing token"
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create Store",
        "tags": [
          "stores"
        ]
      }
    },
    "/stores/{id}/images": {
      "post": {
        "description": "Upload up to 20 images with an optional alt-text map. This API is responsible for uploading the files binaries to your Webround Commerce Store. The Commerce Core APIs can handle data retrieve or asset creation without file upload.",
        "operationId": "StoreController_uploadImages",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "The UUID of the store",
            "schema": {}
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/UploadImageToStoreDTO"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Images successfully uploaded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadImagesResponseDTO"
                }
              }
            }
          },
          "400": {
            "description": "Image Upload Validation Errors",
            "content": {
              "application/json": {
                "examples": {
                  "noImages": {
                    "summary": "No images provided",
                    "value": {
                      "message": "Upload at least one image",
                      "error": "Bad Request",
                      "statusCode": 400
                    }
                  },
                  "tooManyImages": {
                    "summary": "Exceeded limit",
                    "value": {
                      "message": "You can't upload more than 20 images",
                      "error": "Bad Request",
                      "statusCode": 400
                    }
                  },
                  "invalidFile": {
                    "summary": "Invalid file type/size",
                    "value": {
                      "message": "File too large or invalid format",
                      "error": "Bad Request",
                      "statusCode": 400
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Upload Store Images",
        "tags": [
          "stores"
        ]
      }
    },
    "/stores/{id}/images/delete": {
      "post": {
        "description": "Bulk delete images by asset IDs.",
        "operationId": "StoreController_deleteImages",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "The UUID of the store",
            "schema": {}
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteImagesFromStoreDTO"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Images deleted successfully."
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete Store Images",
        "tags": [
          "stores"
        ]
      }
    },
    "/stores/{id}/videos": {
      "post": {
        "description": "Generates a signed URL to upload a video file directly to Bunny.net.",
        "operationId": "StoreController_createVideoUpload",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "The UUID of the store",
            "schema": {}
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateStoreVideoUploadDTO"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Signed URL generated."
          },
          "400": {
            "description": "Storage or Validation issues",
            "content": {
              "application/json": {
                "examples": {
                  "insufficientStorage": {
                    "summary": "Storage Limit Reached",
                    "value": {
                      "message": "Insufficient storage",
                      "error": "Bad Request",
                      "statusCode": 400
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "examples": {
                  "draftNotFound": {
                    "summary": "Draft/Store not found",
                    "value": {
                      "message": "Draft not found",
                      "error": "Not Found",
                      "statusCode": 404
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Third-party integration error",
            "content": {
              "application/json": {
                "examples": {
                  "collectionError": {
                    "summary": "Bunny.net Collection Failure",
                    "value": {
                      "message": "Could not create collection for the store",
                      "error": "Internal Server Error",
                      "statusCode": 500
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Request Video Upload",
        "tags": [
          "stores"
        ]
      }
    },
    "/stores/{id}/videos/{assetId}": {
      "delete": {
        "description": "Permanently deletes a video asset from the store and Bunny.net storage, then synchronizes the used storage space.",
        "operationId": "StoreController_deleteVideo",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "The UUID of the store",
            "schema": {}
          },
          {
            "name": "assetId",
            "required": true,
            "in": "path",
            "description": "The UUID of the specific video asset to delete",
            "schema": {}
          }
        ],
        "responses": {
          "201": {
            "description": "Video successfully deleted and storage synchronized."
          },
          "400": {
            "description": "Bad Request - State issues",
            "content": {
              "application/json": {
                "examples": {
                  "collectionNotInit": {
                    "summary": "Collection missing",
                    "value": {
                      "message": "Collection not initialized",
                      "error": "Bad Request",
                      "statusCode": 400
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "examples": {
                  "draftNotFound": {
                    "summary": "Store/Draft not found",
                    "value": {
                      "message": "Draft not found",
                      "error": "Not Found",
                      "statusCode": 404
                    }
                  },
                  "assetNotFound": {
                    "summary": "Video not found",
                    "value": {
                      "message": "Video asset not found",
                      "error": "Not Found",
                      "statusCode": 404
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete Store Video",
        "tags": [
          "stores"
        ]
      }
    },
    "/hooks/{draftId}": {
      "get": {
        "description": "Returns all hooks configured for a draft, paginated.",
        "operationId": "HookController_findAll",
        "parameters": [
          {
            "name": "draftId",
            "required": true,
            "in": "path",
            "description": "Draft ID",
            "schema": {
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of hooks."
          },
          "404": {
            "description": "Draft not found or no deployment configured."
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List Hooks",
        "tags": [
          "Hooks"
        ]
      },
      "post": {
        "description": "Creates a new hook for a draft. If needsSecret is true, a secret is generated and returned once in plaintext — store it securely.",
        "operationId": "HookController_create",
        "parameters": [
          {
            "name": "draftId",
            "required": true,
            "in": "path",
            "description": "Draft ID",
            "schema": {
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHookDTO"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Hook created. Response includes plaintext secret if needsSecret was true."
          },
          "400": {
            "description": "Slug already used for this draft."
          },
          "404": {
            "description": "Draft not found or no deployment configured."
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create Hook",
        "tags": [
          "Hooks"
        ]
      }
    },
    "/hooks/{draftId}/{hookId}": {
      "patch": {
        "description": "Partially updates a hook. needsSecret cannot be changed after creation.",
        "operationId": "HookController_update",
        "parameters": [
          {
            "name": "draftId",
            "required": true,
            "in": "path",
            "description": "Draft ID",
            "schema": {
              "format": "uuid"
            }
          },
          {
            "name": "hookId",
            "required": true,
            "in": "path",
            "description": "Hook ID",
            "schema": {
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateHookDTO"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hook updated."
          },
          "400": {
            "description": "Slug already used for this draft."
          },
          "404": {
            "description": "Hook not found."
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update Hook",
        "tags": [
          "Hooks"
        ]
      },
      "delete": {
        "description": "Deletes a hook and removes it from the edge proxy configuration.",
        "operationId": "HookController_delete",
        "parameters": [
          {
            "name": "draftId",
            "required": true,
            "in": "path",
            "description": "Draft ID",
            "schema": {
              "format": "uuid"
            }
          },
          {
            "name": "hookId",
            "required": true,
            "in": "path",
            "description": "Hook ID",
            "schema": {
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hook deleted."
          },
          "404": {
            "description": "Hook not found."
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete Hook",
        "tags": [
          "Hooks"
        ]
      }
    }
  },
  "info": {
    "title": "Webround Core APIs",
    "description": "The core API documentation for Webround software",
    "version": "1.0",
    "contact": {}
  },
  "tags": [],
  "servers": [
    {
      "url": "https://core-api.webround.com",
      "description": "Production Server"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "type": "http"
      }
    },
    "schemas": {
      "SubmitFormDTO": {
        "type": "object",
        "properties": {
          "formData": {
            "type": "string",
            "format": "json",
            "description": "JSON string where each key maps to { label: string, value: string }. Must include a `proof` key with a valid Altcha proof-of-work solution obtained from GET /altcha.",
            "example": "{\"name\":{\"label\":\"Name\",\"value\":\"John Doe\"},\"email\":{\"label\":\"Email\",\"value\":\"john-doe@example.com\"},\"proof\":\"altcha_proof_string_here\"}"
          },
          "files": {
            "type": "string",
            "format": "binary",
            "description": "Optional file attachments. Images are automatically compressed before delivery."
          }
        },
        "required": [
          "formData"
        ]
      },
      "CreateStoreDTO": {
        "type": "object",
        "properties": {
          "currencyCode": {
            "type": "string",
            "enum": [
              "EUR",
              "USD",
              "GBP",
              "CHF"
            ]
          },
          "countryCode": {
            "type": "string",
            "enum": [
              "AF",
              "AX",
              "AL",
              "DZ",
              "AS",
              "AD",
              "AO",
              "AI",
              "AQ",
              "AG",
              "AR",
              "AM",
              "AW",
              "AU",
              "AT",
              "AZ",
              "BS",
              "BH",
              "BD",
              "BB",
              "BY",
              "BE",
              "BZ",
              "BJ",
              "BM",
              "BT",
              "BO",
              "BQ",
              "BA",
              "BW",
              "BV",
              "BR",
              "IO",
              "BN",
              "BG",
              "BF",
              "BI",
              "CV",
              "KH",
              "CM",
              "CA",
              "KY",
              "CF",
              "TD",
              "CL",
              "CN",
              "CX",
              "CC",
              "CO",
              "KM",
              "CG",
              "CD",
              "CK",
              "CR",
              "CI",
              "HR",
              "CU",
              "CW",
              "CY",
              "CZ",
              "DK",
              "DJ",
              "DM",
              "DO",
              "EC",
              "EG",
              "SV",
              "GQ",
              "ER",
              "EE",
              "ET",
              "FK",
              "FO",
              "FJ",
              "FI",
              "FR",
              "GF",
              "PF",
              "TF",
              "GA",
              "GM",
              "GE",
              "DE",
              "GH",
              "GI",
              "GR",
              "GL",
              "GD",
              "GP",
              "GU",
              "GT",
              "GG",
              "GN",
              "GW",
              "GY",
              "HT",
              "HM",
              "VA",
              "HN",
              "HK",
              "HU",
              "IS",
              "IN",
              "ID",
              "IR",
              "IQ",
              "IE",
              "IM",
              "IL",
              "IT",
              "JM",
              "JP",
              "JE",
              "JO",
              "KZ",
              "KE",
              "KI",
              "KP",
              "KR",
              "KW",
              "KG",
              "LA",
              "LV",
              "LB",
              "LS",
              "LR",
              "LY",
              "LI",
              "LT",
              "LU",
              "MO",
              "MK",
              "MG",
              "MW",
              "MY",
              "MV",
              "ML",
              "MT",
              "MH",
              "MQ",
              "MR",
              "MU",
              "YT",
              "MX",
              "FM",
              "MD",
              "MC",
              "MN",
              "ME",
              "MS",
              "MA",
              "MZ",
              "MM",
              "NA",
              "NR",
              "NP",
              "NL",
              "NC",
              "NZ",
              "NI",
              "NE",
              "NG",
              "NU",
              "NF",
              "MP",
              "NO",
              "OM",
              "PK",
              "PW",
              "PS",
              "PA",
              "PG",
              "PY",
              "PE",
              "PH",
              "PN",
              "PL",
              "PT",
              "PR",
              "QA",
              "RE",
              "RO",
              "RU",
              "RW",
              "BL",
              "SH",
              "KN",
              "LC",
              "MF",
              "PM",
              "VC",
              "WS",
              "SM",
              "ST",
              "SA",
              "SN",
              "RS",
              "SC",
              "SL",
              "SG",
              "SX",
              "SK",
              "SI",
              "SB",
              "SO",
              "ZA",
              "GS",
              "SS",
              "ES",
              "LK",
              "SD",
              "SR",
              "SJ",
              "SZ",
              "SE",
              "CH",
              "SY",
              "TW",
              "TJ",
              "TZ",
              "TH",
              "TL",
              "TG",
              "TK",
              "TO",
              "TT",
              "TN",
              "TR",
              "TM",
              "TC",
              "TV",
              "UG",
              "UA",
              "AE",
              "GB",
              "US",
              "UM",
              "UY",
              "UZ",
              "VU",
              "VE",
              "VN",
              "VG",
              "VI",
              "WF",
              "EH",
              "YE",
              "ZM",
              "ZW"
            ]
          }
        },
        "required": [
          "currencyCode",
          "countryCode"
        ]
      },
      "UploadImageToStoreDTO": {
        "type": "object",
        "properties": {
          "image": {
            "type": "string",
            "format": "binary",
            "description": "The image files"
          },
          "altMap": {
            "type": "object",
            "description": "Maps that associates alt descriptions to files. In your website, images will use product names, but you can filter by alt text in the back-office",
            "example": {
              "image1.jpg": "Alt for image1",
              "logo.png": "Official logo"
            }
          }
        },
        "required": [
          "image",
          "altMap"
        ]
      },
      "AssetItemDTO": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "storeId": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "fileName": {
            "type": "string"
          },
          "size": {
            "type": "number",
            "nullable": true
          },
          "alt": {
            "type": "string",
            "nullable": true
          },
          "thumbnail": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "format": "date-time",
            "type": "string",
            "nullable": true
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "id",
          "storeId",
          "url",
          "type",
          "fileName"
        ]
      },
      "UploadImagesResponseDataDTO": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AssetItemDTO"
            }
          }
        },
        "required": [
          "success",
          "items"
        ]
      },
      "UploadImagesResponseDTO": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/UploadImagesResponseDataDTO"
          }
        },
        "required": [
          "data"
        ]
      },
      "DeleteImageAssetDTO": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "DeleteImagesFromStoreDTO": {
        "type": "object",
        "properties": {
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeleteImageAssetDTO"
            }
          }
        },
        "required": [
          "assets"
        ]
      },
      "CreateStoreVideoUploadDTO": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "size": {
            "type": "number"
          },
          "alt": {
            "type": "string"
          }
        },
        "required": [
          "title",
          "size",
          "alt"
        ]
      },
      "CreateHookDTO": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "example": "my-hook"
          },
          "url": {
            "type": "string",
            "example": "https://app.example.com"
          },
          "method": {
            "type": "string",
            "enum": [
              "GET",
              "POST",
              "PUT",
              "PATCH",
              "DELETE"
            ],
            "default": "GET"
          },
          "body": {
            "type": "string",
            "nullable": true,
            "default": null
          },
          "inject": {
            "type": "string",
            "enum": [
              "window",
              "head-script",
              "head-style",
              "json-ld",
              "body-start",
              "body-end"
            ],
            "default": "window"
          },
          "pages": {
            "default": [
              "*"
            ],
            "example": [
              "*"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "timeout": {
            "type": "number",
            "minimum": 500,
            "maximum": 10000,
            "default": 3000
          },
          "targetAudience": {
            "type": "string",
            "enum": [
              "all",
              "human",
              "bot"
            ],
            "default": "all"
          },
          "needsSecret": {
            "type": "boolean",
            "default": false
          }
        },
        "required": [
          "slug",
          "url"
        ]
      },
      "UpdateHookDTO": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "example": "my-hook"
          },
          "url": {
            "type": "string",
            "example": "https://app.example.com"
          },
          "method": {
            "type": "string",
            "enum": [
              "GET",
              "POST",
              "PUT",
              "PATCH",
              "DELETE"
            ]
          },
          "body": {
            "type": "string",
            "nullable": true
          },
          "inject": {
            "type": "string",
            "enum": [
              "window",
              "head-script",
              "head-style",
              "json-ld",
              "body-start",
              "body-end"
            ]
          },
          "pages": {
            "example": [
              "*"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "timeout": {
            "type": "number",
            "minimum": 500,
            "maximum": 10000
          },
          "targetAudience": {
            "type": "string",
            "enum": [
              "all",
              "human",
              "bot"
            ]
          }
        }
      }
    }
  }
}