{
  "openapi": "3.1.0",
  "info": {
    "title": "Hopla API",
    "version": "2.0.0",
    "summary": "Read and update a Hopla page programmatically.",
    "description": "Manage a Hopla page from your code, with the features of the admin panel: profile, theme, social links, blocks, images, analytics, email list, QR code, and metadata fetching for websites, feeds and review pages. Requires the Pro plan (or the 14-day trial). Requests and responses are JSON, UTF-8 encoded.\n\nCreate an API key in Réglages (Settings), \"API et MCP\", on [app.hopla.page](https://app.hopla.page/admin), then send it in the `Authorization: Bearer hopla_…` header. Guides: [hopla.page/docs](https://hopla.page/docs).\n\nAPI v1 (`/api/v1`, French field names) remains supported for existing integrations: see [Migrating from v1](https://hopla.page/docs/migrating-from-v1).",
    "contact": {
      "name": "Hopla",
      "url": "https://hopla.page/docs"
    },
    "license": {
      "name": "AGPL-3.0",
      "identifier": "AGPL-3.0-only"
    }
  },
  "servers": [
    {
      "url": "https://app.hopla.page/api/v2",
      "description": "Production"
    }
  ],
  "security": [
    {
      "cle": []
    }
  ],
  "tags": [
    {
      "name": "Page",
      "description": "Full page and its settings: profile, theme, social links, colors, SEO."
    },
    {
      "name": "Blocks",
      "description": "Page blocks: links, headings, text, videos, products, FAQ…"
    },
    {
      "name": "Analytics",
      "description": "Views, visitors and clicks, cookieless: visitor IP addresses are never stored."
    },
    {
      "name": "Sharing",
      "description": "QR code of the page."
    },
    {
      "name": "Images",
      "description": "Upload images for profile photos, thumbnails, galleries or backgrounds."
    },
    {
      "name": "Email list",
      "description": "Subscribers collected by the email signup block: list, export, delete, sync to a newsletter."
    },
    {
      "name": "Web fetching",
      "description": "Metadata the admin panel reads from a pasted URL: website title and image, feed posts, review rating."
    },
    {
      "name": "Account",
      "description": "Plan, trial, included features, API keys and data export."
    },
    {
      "name": "Reference values",
      "description": "Valid values for themes, social networks, icons and block types."
    }
  ],
  "paths": {
    "/page": {
      "get": {
        "tags": [
          "Page"
        ],
        "operationId": "getPage",
        "summary": "Get page",
        "description": "Full page: URL, plan, settings, and blocks in display order with their ids.",
        "responses": {
          "200": {
            "description": "Page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page"
                },
                "example": {
                  "page": {
                    "username": "camille",
                    "url": "https://hopla.page/camille",
                    "plan": "pro"
                  },
                  "settings": {
                    "profile": {
                      "name": "Camille Durand",
                      "bio": "Coach sportive à Lyon",
                      "avatar": "https://hopla.page/media/k3v9.webp",
                      "badge": false,
                      "photo": "round"
                    },
                    "socialLinks": [
                      {
                        "network": "instagram",
                        "url": "https://instagram.com/camille"
                      },
                      {
                        "network": "youtube",
                        "url": "https://youtube.com/@camille"
                      }
                    ],
                    "socialLinksPosition": "top",
                    "theme": "onyx",
                    "colors": {
                      "background": "",
                      "buttons": "",
                      "accent": ""
                    },
                    "button": {
                      "style": "solid",
                      "shape": "pill",
                      "shadow": "subtle"
                    },
                    "seo": {
                      "title": "",
                      "description": "",
                      "indexable": true
                    },
                    "shareButton": true,
                    "openInApps": true,
                    "weeklySummary": true,
                    "formal": false,
                    "animation": "shine",
                    "networkViews": [],
                    "desktopLayout": "column",
                    "utm": false,
                    "redirect": {
                      "url": "",
                      "endsAt": ""
                    },
                    "font": "",
                    "backgroundImage": "",
                    "domain": ""
                  },
                  "blocks": [
                    {
                      "id": "a1b2c3d4e5f6",
                      "type": "link",
                      "title": "Mon nouveau podcast",
                      "url": "https://example.com/podcast",
                      "subtitle": "",
                      "description": "",
                      "icon": "micro",
                      "image": "",
                      "visible": true,
                      "indexable": true,
                      "featured": false,
                      "format": "banner",
                      "tint": "accent",
                      "animation": "auto",
                      "startsAt": "",
                      "endsAt": "",
                      "countdown": false,
                      "schedule": null,
                      "audience": null,
                      "hiddenFor": [],
                      "askFirstName": false,
                      "access": null,
                      "proof": null,
                      "price": "",
                      "amounts": [],
                      "partnership": null,
                      "questions": [],
                      "images": [],
                      "contact": null,
                      "address": "",
                      "sensitive": false,
                      "layout": "list",
                      "countries": null,
                      "gift": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/settings": {
      "get": {
        "tags": [
          "Page"
        ],
        "operationId": "getSettings",
        "summary": "Get settings",
        "description": "Settings only, without blocks: profile, social links, theme, colors, buttons, SEO, redirect.",
        "responses": {
          "200": {
            "description": "Settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "settings": {
                      "$ref": "#/components/schemas/Settings"
                    }
                  }
                },
                "example": {
                  "settings": {
                    "profile": {
                      "name": "Camille Durand",
                      "bio": "Coach sportive à Lyon",
                      "avatar": "https://hopla.page/media/k3v9.webp",
                      "badge": false,
                      "photo": "round"
                    },
                    "socialLinks": [
                      {
                        "network": "instagram",
                        "url": "https://instagram.com/camille"
                      },
                      {
                        "network": "youtube",
                        "url": "https://youtube.com/@camille"
                      }
                    ],
                    "socialLinksPosition": "top",
                    "theme": "onyx",
                    "colors": {
                      "background": "",
                      "buttons": "",
                      "accent": ""
                    },
                    "button": {
                      "style": "solid",
                      "shape": "pill",
                      "shadow": "subtle"
                    },
                    "seo": {
                      "title": "",
                      "description": "",
                      "indexable": true
                    },
                    "shareButton": true,
                    "openInApps": true,
                    "weeklySummary": true,
                    "formal": false,
                    "animation": "shine",
                    "networkViews": [],
                    "desktopLayout": "column",
                    "utm": false,
                    "redirect": {
                      "url": "",
                      "endsAt": ""
                    },
                    "font": "",
                    "backgroundImage": "",
                    "domain": ""
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Page"
        ],
        "operationId": "updateSettings",
        "summary": "Update settings",
        "description": "Partially update settings; omitted fields are unchanged. `profile`, `button`, `seo`, `colors` and `redirect` are merged field by field; `socialLinks` is replaced as a whole. Requires a key with write access.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SettingsUpdate"
              },
              "example": {
                "profile": {
                  "bio": "Coach sportive à Lyon"
                },
                "theme": "onyx"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "settings": {
                      "$ref": "#/components/schemas/Settings"
                    }
                  }
                },
                "example": {
                  "settings": {
                    "profile": {
                      "name": "Camille Durand",
                      "bio": "Coach sportive à Lyon",
                      "avatar": "https://hopla.page/media/k3v9.webp",
                      "badge": false,
                      "photo": "round"
                    },
                    "socialLinks": [
                      {
                        "network": "instagram",
                        "url": "https://instagram.com/camille"
                      },
                      {
                        "network": "youtube",
                        "url": "https://youtube.com/@camille"
                      }
                    ],
                    "socialLinksPosition": "top",
                    "theme": "onyx",
                    "colors": {
                      "background": "",
                      "buttons": "",
                      "accent": ""
                    },
                    "button": {
                      "style": "solid",
                      "shape": "pill",
                      "shadow": "subtle"
                    },
                    "seo": {
                      "title": "",
                      "description": "",
                      "indexable": true
                    },
                    "shareButton": true,
                    "openInApps": true,
                    "weeklySummary": true,
                    "formal": false,
                    "animation": "shine",
                    "networkViews": [],
                    "desktopLayout": "column",
                    "utm": false,
                    "redirect": {
                      "url": "",
                      "endsAt": ""
                    },
                    "font": "",
                    "backgroundImage": "",
                    "domain": ""
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: field, date or block type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "block_rejected",
                    "message": "Scheduling a block (start or end date) requires the Starter plan or higher."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/blocks": {
      "get": {
        "tags": [
          "Blocks"
        ],
        "operationId": "listBlocks",
        "summary": "List blocks",
        "description": "Blocks, in display order.",
        "responses": {
          "200": {
            "description": "Blocks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "blocks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Block"
                      }
                    }
                  }
                },
                "example": {
                  "blocks": [
                    {
                      "id": "a1b2c3d4e5f6",
                      "type": "link",
                      "title": "Mon nouveau podcast",
                      "url": "https://example.com/podcast",
                      "subtitle": "",
                      "description": "",
                      "icon": "micro",
                      "image": "",
                      "visible": true,
                      "indexable": true,
                      "featured": false,
                      "format": "banner",
                      "tint": "accent",
                      "animation": "auto",
                      "startsAt": "",
                      "endsAt": "",
                      "countdown": false,
                      "schedule": null,
                      "audience": null,
                      "hiddenFor": [],
                      "askFirstName": false,
                      "access": null,
                      "proof": null,
                      "price": "",
                      "amounts": [],
                      "partnership": null,
                      "questions": [],
                      "images": [],
                      "contact": null,
                      "address": "",
                      "sensitive": false,
                      "layout": "list",
                      "countries": null,
                      "gift": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Blocks"
        ],
        "operationId": "createBlock",
        "summary": "Create block",
        "description": "Create a block at the top of the page, or at `position` (zero-based, 0 = top). Without `type`, creates a link.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlockCreate"
              },
              "example": {
                "type": "link",
                "title": "Mon nouveau podcast",
                "url": "https://example.com/podcast",
                "position": 2
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created block, with its id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "block": {
                      "$ref": "#/components/schemas/Block"
                    }
                  }
                },
                "example": {
                  "block": {
                    "id": "a1b2c3d4e5f6",
                    "type": "link",
                    "title": "Mon nouveau podcast",
                    "url": "https://example.com/podcast",
                    "subtitle": "",
                    "description": "",
                    "icon": "micro",
                    "image": "",
                    "visible": true,
                    "indexable": true,
                    "featured": false,
                    "format": "banner",
                    "tint": "accent",
                    "animation": "auto",
                    "startsAt": "",
                    "endsAt": "",
                    "countdown": false,
                    "schedule": null,
                    "audience": null,
                    "hiddenFor": [],
                    "askFirstName": false,
                    "access": null,
                    "proof": null,
                    "price": "",
                    "amounts": [],
                    "partnership": null,
                    "questions": [],
                    "images": [],
                    "contact": null,
                    "address": "",
                    "sensitive": false,
                    "layout": "list",
                    "countries": null,
                    "gift": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: field, date or block type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "block_rejected",
                    "message": "Scheduling a block (start or end date) requires the Starter plan or higher."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/blocks/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Block id, from GET /page or GET /blocks.",
          "schema": {
            "type": "string",
            "example": "a1b2c3d4e5f6"
          }
        }
      ],
      "get": {
        "tags": [
          "Blocks"
        ],
        "operationId": "getBlock",
        "summary": "Get block",
        "responses": {
          "200": {
            "description": "Block.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "block": {
                      "$ref": "#/components/schemas/Block"
                    }
                  }
                },
                "example": {
                  "block": {
                    "id": "a1b2c3d4e5f6",
                    "type": "link",
                    "title": "Mon nouveau podcast",
                    "url": "https://example.com/podcast",
                    "subtitle": "",
                    "description": "",
                    "icon": "micro",
                    "image": "",
                    "visible": true,
                    "indexable": true,
                    "featured": false,
                    "format": "banner",
                    "tint": "accent",
                    "animation": "auto",
                    "startsAt": "",
                    "endsAt": "",
                    "countdown": false,
                    "schedule": null,
                    "audience": null,
                    "hiddenFor": [],
                    "askFirstName": false,
                    "access": null,
                    "proof": null,
                    "price": "",
                    "amounts": [],
                    "partnership": null,
                    "questions": [],
                    "images": [],
                    "contact": null,
                    "address": "",
                    "sensitive": false,
                    "layout": "list",
                    "countries": null,
                    "gift": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Block not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "block_not_found",
                    "message": "Block not found."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Blocks"
        ],
        "operationId": "updateBlock",
        "summary": "Update block",
        "description": "Partially update a block; omitted fields are unchanged. Type and id are immutable. `{\"visible\": false}` hides the block without deleting it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlockUpdate"
              },
              "example": {
                "visible": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated block.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "block": {
                      "$ref": "#/components/schemas/Block"
                    }
                  }
                },
                "example": {
                  "block": {
                    "id": "a1b2c3d4e5f6",
                    "type": "link",
                    "title": "Mon nouveau podcast",
                    "url": "https://example.com/podcast",
                    "subtitle": "",
                    "description": "",
                    "icon": "micro",
                    "image": "",
                    "visible": false,
                    "indexable": true,
                    "featured": false,
                    "format": "banner",
                    "tint": "accent",
                    "animation": "auto",
                    "startsAt": "",
                    "endsAt": "",
                    "countdown": false,
                    "schedule": null,
                    "audience": null,
                    "hiddenFor": [],
                    "askFirstName": false,
                    "access": null,
                    "proof": null,
                    "price": "",
                    "amounts": [],
                    "partnership": null,
                    "questions": [],
                    "images": [],
                    "contact": null,
                    "address": "",
                    "sensitive": false,
                    "layout": "list",
                    "countries": null,
                    "gift": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: field, date or block type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "block_rejected",
                    "message": "Scheduling a block (start or end date) requires the Starter plan or higher."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Block not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "block_not_found",
                    "message": "Block not found."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Blocks"
        ],
        "operationId": "deleteBlock",
        "summary": "Delete block",
        "description": "Permanently delete a block. To hide it instead, use PATCH with `{\"visible\": false}`.",
        "responses": {
          "200": {
            "description": "Block deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "deleted": "a1b2c3d4e5f6"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Block not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "block_not_found",
                    "message": "Block not found."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/blocks/{id}/duplicate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Id of the block to duplicate.",
          "schema": {
            "type": "string",
            "example": "a1b2c3d4e5f6"
          }
        }
      ],
      "post": {
        "tags": [
          "Blocks"
        ],
        "operationId": "duplicateBlock",
        "summary": "Duplicate block",
        "description": "Create a copy of the block right below it, with a new id. `hidden: true` creates the copy hidden.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hidden": {
                    "type": "boolean",
                    "description": "Create the copy hidden."
                  }
                }
              },
              "example": {
                "hidden": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Copy.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "block": {
                      "$ref": "#/components/schemas/Block"
                    }
                  }
                },
                "example": {
                  "block": {
                    "id": "f6e5d4c3b2a1",
                    "type": "link",
                    "title": "Mon nouveau podcast",
                    "url": "https://example.com/podcast",
                    "subtitle": "",
                    "description": "",
                    "icon": "micro",
                    "image": "",
                    "visible": false,
                    "indexable": true,
                    "featured": false,
                    "format": "banner",
                    "tint": "accent",
                    "animation": "auto",
                    "startsAt": "",
                    "endsAt": "",
                    "countdown": false,
                    "schedule": null,
                    "audience": null,
                    "hiddenFor": [],
                    "askFirstName": false,
                    "access": null,
                    "proof": null,
                    "price": "",
                    "amounts": [],
                    "partnership": null,
                    "questions": [],
                    "images": [],
                    "contact": null,
                    "address": "",
                    "sensitive": false,
                    "layout": "list",
                    "countries": null,
                    "gift": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: field, date or block type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "block_rejected",
                    "message": "Scheduling a block (start or end date) requires the Starter plan or higher."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Block not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "block_not_found",
                    "message": "Block not found."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/blocks/order": {
      "put": {
        "tags": [
          "Blocks"
        ],
        "operationId": "reorderBlocks",
        "summary": "Reorder blocks",
        "description": "New order of all blocks: every block id exactly once, first = top.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "ids": [
                  "a1b2c3d4e5f6",
                  "f6e5d4c3b2a1"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Blocks, in their new order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "blocks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Block"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "An id is missing, duplicated or does not belong to the page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_order",
                    "message": "ids must list every block id exactly once, in the new order."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/analytics": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "operationId": "getAnalytics",
        "summary": "Get analytics",
        "description": "Views, visitors and clicks, by day, block, referrer, device and country, compared with the previous period. Use a period or a date range. The period is capped to the analytics retention of the current plan.",
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "description": "Number of past days.",
            "schema": {
              "type": "integer",
              "enum": [
                7,
                14,
                30,
                90,
                365
              ],
              "default": 30
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start date, YYYY-MM-DD, used with `to`.",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End date, YYYY-MM-DD.",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-10-15"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Analytics.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "analytics": {
                      "$ref": "#/components/schemas/Analytics"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid period, or outside the retention period.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_period",
                    "message": "Invalid period: use 7, 14, 30, 90 or 365 days, or from and to dates (YYYY-MM-DD)."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/analytics/week": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "operationId": "getWeeklyViews",
        "summary": "Get weekly views",
        "description": "Views for the last 7 days and the 7 days before. Available on all plans, including those without analytics history.",
        "responses": {
          "200": {
            "description": "Weekly views.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "week": {
                      "type": "object",
                      "properties": {
                        "views": {
                          "type": "integer",
                          "description": "Views in the last 7 days."
                        },
                        "previous": {
                          "type": "integer",
                          "description": "Views in the previous 7 days."
                        },
                        "everViewed": {
                          "type": "boolean",
                          "description": "The page has had at least one view."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "week": {
                    "views": 412,
                    "previous": 377,
                    "everViewed": true
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/analytics/summary": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "operationId": "getAnalyticsSummary",
        "summary": "Get analytics summary",
        "description": "Human-readable summary of the period, in French, as shown in the admin panel: one sentence and its highlights.",
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "description": "Number of past days.",
            "schema": {
              "type": "integer",
              "enum": [
                7,
                14,
                30,
                90,
                365
              ],
              "default": 30
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start date, YYYY-MM-DD, used with `to`.",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End date, YYYY-MM-DD.",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-10-15"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "object",
                      "properties": {
                        "sentence": {
                          "type": "string",
                          "description": "Summary sentence, ready to display."
                        },
                        "highlights": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "text": {
                                "type": "string"
                              },
                              "trend": {
                                "type": "string",
                                "enum": [
                                  "up",
                                  "down",
                                  "neutral"
                                ],
                                "description": "Trend: up, down or neutral."
                              }
                            }
                          }
                        },
                        "days": {
                          "type": "integer",
                          "description": "Period length, in days."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "summary": {
                    "sentence": "En 30 jours, 1 284 vues, 12 % de plus que les 30 jours d’avant.",
                    "highlights": [
                      {
                        "text": "Ta page a eu 1 284 vues, 12 % de plus que les 30 jours d’avant.",
                        "trend": "up"
                      }
                    ],
                    "days": 30
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/qr-code": {
      "get": {
        "tags": [
          "Sharing"
        ],
        "operationId": "getQrCode",
        "summary": "Get QR code",
        "description": "Links to the page URL with `?s=qr`, so scans are tracked separately in analytics.",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "svg",
                "png"
              ],
              "default": "svg"
            }
          },
          {
            "name": "size",
            "in": "query",
            "description": "Size, in pixels.",
            "schema": {
              "type": "integer",
              "minimum": 120,
              "maximum": 2000,
              "default": 720
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Image.",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/media": {
      "post": {
        "tags": [
          "Images"
        ],
        "operationId": "uploadImage",
        "summary": "Upload image",
        "description": "Upload a WebP, JPEG or PNG image, 1.5 MB maximum, hosted by Hopla. Send the raw bytes (`Content-Type: image/png`…), or JSON `{\"image\": \"data:image/png;base64,…\"}`. Use the returned `path` in `profil.avatar`, a block `image`, gallery `images` or `imageFond`.",
        "requestBody": {
          "required": true,
          "content": {
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/jpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/webp": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "description": "Data URL.",
                    "example": "data:image/png;base64,iVBORw0KGgo…"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Image uploaded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "media": {
                      "type": "object",
                      "properties": {
                        "path": {
                          "type": "string",
                          "description": "Path to use in a block or in settings."
                        },
                        "url": {
                          "type": "string",
                          "description": "Absolute URL."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "media": {
                    "path": "/media/054a3c6991bc.png",
                    "url": "https://hopla.page/media/054a3c6991bc.png"
                  }
                }
              }
            }
          },
          "400": {
            "description": "No image in the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "missing_image",
                    "message": "The image field must be a data URL: data:image/webp;base64,…"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Image over 1.5 MB, or storage quota exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "image_too_large",
                    "message": "Image too large: 1.5 MB maximum."
                  }
                }
              }
            }
          },
          "415": {
            "description": "Format other than WebP, JPEG or PNG.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unsupported_image_format",
                    "message": "Unsupported image format: WebP, JPEG or PNG only."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/email-list": {
      "get": {
        "tags": [
          "Email list"
        ],
        "operationId": "getEmailList",
        "summary": "Get email list summary",
        "description": "Total subscribers, new subscribers this week, subscribers pending sync, and the connected integration. The integration credential is never returned.",
        "responses": {
          "200": {
            "description": "Email list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "list": {
                      "type": "object",
                      "properties": {
                        "subscribers": {
                          "type": "integer",
                          "description": "Total subscribers."
                        },
                        "thisWeek": {
                          "type": "integer",
                          "description": "Subscribers in the last 7 days."
                        },
                        "pending": {
                          "type": "integer",
                          "description": "Not yet synced."
                        },
                        "connected": {
                          "anyOf": [
                            {
                              "type": "object",
                              "properties": {
                                "tool": {
                                  "type": "string",
                                  "description": "Integration: brevo, mailchimp, kit, make…"
                                },
                                "list": {
                                  "type": "string",
                                  "description": "Name of the selected list."
                                },
                                "key": {
                                  "type": "string",
                                  "description": "Masked credential prefix."
                                }
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      }
                    }
                  }
                },
                "example": {
                  "list": {
                    "subscribers": 128,
                    "thisWeek": 9,
                    "pending": 0,
                    "connected": {
                      "tool": "brevo",
                      "list": "Newsletter",
                      "key": "xkeysib-…a3f"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/subscribers": {
      "get": {
        "tags": [
          "Email list"
        ],
        "operationId": "listSubscribers",
        "summary": "List subscribers",
        "description": "Subscribers, newest first, paginated. `format=csv` returns the full CSV export (email, prenom, inscrit, source).",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Page size.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Offset: the `next` value from the previous page; 0 for the first page.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of subscribers, or the CSV file.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscribers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Subscriber"
                      }
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total subscribers."
                    },
                    "next": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Next offset, to pass as `offset`; null on the last page."
                    }
                  }
                },
                "example": {
                  "subscribers": [
                    {
                      "id": "92c5ca872beb",
                      "email": "ana@example.com",
                      "firstName": "Ana",
                      "subscribedAt": "2026-10-27T18:02:11.000Z",
                      "source": "instagram",
                      "synced": false
                    }
                  ],
                  "total": 128,
                  "next": 100
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "email,prenom,inscrit,source\nana@example.com,Ana,2026-10-27T18:02:11.000Z,instagram\n"
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/subscribers/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "example": "92c5ca872beb"
          }
        }
      ],
      "delete": {
        "tags": [
          "Email list"
        ],
        "operationId": "deleteSubscriber",
        "summary": "Delete subscriber",
        "description": "Permanently delete a subscriber from Hopla (for example on an erasure request). The copy already synced to the newsletter is kept.",
        "responses": {
          "200": {
            "description": "Subscriber deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "deleted": "92c5ca872beb"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Subscriber not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "subscriber_not_found",
                    "message": "Subscriber not found."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/subscribers/sync": {
      "post": {
        "tags": [
          "Email list"
        ],
        "operationId": "syncSubscribers",
        "summary": "Sync pending subscribers",
        "description": "Push pending subscribers to the connected newsletter (Brevo, Mailchimp, Kit…) or webhook (Make, Zapier, n8n).",
        "responses": {
          "200": {
            "description": "Sync result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "synced": {
                      "type": "integer",
                      "description": "Subscribers sent."
                    },
                    "failed": {
                      "type": "integer",
                      "description": "Subscribers rejected by the integration."
                    }
                  }
                },
                "example": {
                  "synced": 9,
                  "failed": 0
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "409": {
            "description": "No integration connected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "no_integration",
                    "message": "No newsletter or automation is connected. Connect one in Intégrations (Integrations) on app.hopla.page."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Sync rate limit exceeded: 20 requests per 10 minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many sync requests. Retry in a few minutes."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/fetch/preview": {
      "post": {
        "tags": [
          "Web fetching"
        ],
        "operationId": "fetchUrlMetadata",
        "summary": "Fetch URL metadata",
        "description": "Title, description, price (for product pages) and image of a URL, as the admin panel does when a URL is pasted. `image`: `true` (social sharing image or favicon), `false`, `\"share\"` (sharing image), `\"logo\"` or `\"thumbnail\"`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "URL to fetch (https://).",
                    "example": "https://example.com"
                  },
                  "image": {
                    "description": "Image to return.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "string",
                        "enum": [
                          "share",
                          "logo",
                          "thumbnail"
                        ]
                      }
                    ]
                  }
                }
              },
              "example": {
                "url": "https://example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Page metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "preview": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "price": {
                          "type": "string"
                        },
                        "image": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Image as a data URL; null if none."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "preview": {
                    "title": "Example Domain",
                    "description": "",
                    "price": "",
                    "image": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "url is not an http(s) URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_url",
                    "message": "url must be an https:// or http:// URL."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The site did not respond, or did not return HTML.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "fetch_failed",
                    "message": "Unable to fetch this URL."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Web fetch rate limit exceeded: 30 requests per 10 minutes per account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many web fetch requests. Retry in a few minutes."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/fetch/feed": {
      "post": {
        "tags": [
          "Web fetching"
        ],
        "operationId": "fetchFeed",
        "summary": "Fetch feed",
        "description": "Latest three posts of a blog or podcast (RSS, Atom, or a page that declares its feed), or with `video: true` the latest long-form video of a YouTube channel.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "URL to fetch (https://).",
                    "example": "https://example.com/blog"
                  },
                  "video": {
                    "type": "boolean",
                    "description": "YouTube channel URL: return its latest video."
                  }
                }
              },
              "example": {
                "url": "https://example.com/blog"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Posts; an empty list if nothing could be read.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "articles": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "date": {
                            "type": "string"
                          },
                          "image": {
                            "type": "string"
                          },
                          "video": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "articles": [
                    {
                      "title": "Mon dernier épisode",
                      "url": "https://example.com/blog/episode-12",
                      "date": "2026-10-27T08:00:00.000Z",
                      "image": "",
                      "video": ""
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "url is not an http(s) URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_url",
                    "message": "url must be an https:// or http:// URL."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Web fetch rate limit exceeded: 30 requests per 10 minutes per account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many web fetch requests. Retry in a few minutes."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/fetch/rating": {
      "post": {
        "tags": [
          "Web fetching"
        ],
        "operationId": "fetchReviewRating",
        "summary": "Fetch review rating",
        "description": "Rating of a Trustpilot page, or of any page declaring it (JSON-LD `aggregateRating`), normalized to 5. `null` if the page has no rating.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "URL to fetch (https://).",
                    "example": "https://fr.trustpilot.com/review/example.com"
                  }
                }
              },
              "example": {
                "url": "https://fr.trustpilot.com/review/example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rating.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rating": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "rating": {
                              "type": "number"
                            },
                            "count": {
                              "type": "integer"
                            },
                            "source": {
                              "type": "string"
                            }
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "rating": {
                    "rating": 4.6,
                    "count": 1820,
                    "source": "Trustpilot"
                  }
                }
              }
            }
          },
          "400": {
            "description": "url is not an http(s) URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_url",
                    "message": "url must be an https:// or http:// URL."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Web fetch rate limit exceeded: 30 requests per 10 minutes per account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many web fetch requests. Retry in a few minutes."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccount",
        "summary": "Get account",
        "description": "Current plan, trial, subscription, complimentary plan, and for each gated feature whether it is included and which plan unlocks it.",
        "responses": {
          "200": {
            "description": "Account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "$ref": "#/components/schemas/Account"
                    }
                  }
                },
                "example": {
                  "account": {
                    "username": "camille",
                    "url": "https://hopla.page/camille",
                    "createdAt": "2026-10-20T09:12:00.000Z",
                    "plan": "pro",
                    "trial": null,
                    "subscription": {
                      "plan": "pro",
                      "status": "active",
                      "period": "month",
                      "periodEnd": "2026-11-20T09:12:00.000Z"
                    },
                    "complimentaryPlan": null,
                    "features": {
                      "api": {
                        "included": true,
                        "plan": "pro"
                      },
                      "customColors": {
                        "included": true,
                        "plan": "starter"
                      }
                    },
                    "analyticsRetentionDays": 365
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/api-keys": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "listApiKeys",
        "summary": "List API keys",
        "description": "API keys of the account, never the secret itself: name, prefix, permissions and last use. `current` marks the key used for this request.",
        "responses": {
          "200": {
            "description": "API keys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiKeys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "prefix": {
                            "type": "string"
                          },
                          "writeAccess": {
                            "type": "boolean"
                          },
                          "createdAt": {
                            "type": "string"
                          },
                          "lastUsedAt": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "current": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "apiKeys": [
                    {
                      "id": "2916f190dae2",
                      "name": "Mon script",
                      "prefix": "hopla_hT-a",
                      "writeAccess": true,
                      "createdAt": "2026-10-28T10:00:00.000Z",
                      "lastUsedAt": "2026-10-28T21:40:00.000Z",
                      "current": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/export": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "exportAccount",
        "summary": "Export account data",
        "description": "All data Hopla stores for the account, as a JSON file (GDPR right of access), images excluded.",
        "responses": {
          "200": {
            "description": "JSON file, served as an attachment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/social-networks": {
      "get": {
        "tags": [
          "Reference values"
        ],
        "operationId": "listSocialNetworks",
        "summary": "List social networks",
        "responses": {
          "200": {
            "description": "Social networks, with their display names.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "socialNetworks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "enum": [
                              "youtube",
                              "linkedin",
                              "instagram",
                              "tiktok",
                              "x",
                              "github",
                              "facebook",
                              "threads",
                              "twitch",
                              "spotify",
                              "email",
                              "site",
                              "whatsapp",
                              "telegram",
                              "discord",
                              "pinterest",
                              "snapchat",
                              "bluesky",
                              "mastodon",
                              "reddit",
                              "substack",
                              "medium",
                              "patreon",
                              "soundcloud",
                              "deezer",
                              "applemusic",
                              "applepodcasts",
                              "bandcamp",
                              "vimeo",
                              "behance",
                              "dribbble",
                              "malt",
                              "etsy"
                            ]
                          },
                          "name": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "socialNetworks": [
                    {
                      "id": "instagram",
                      "name": "Instagram"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/icons": {
      "get": {
        "tags": [
          "Reference values"
        ],
        "operationId": "listIcons",
        "summary": "List icons",
        "description": "Valid values for the `icon` field of a block.",
        "responses": {
          "200": {
            "description": "Icons.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "icons": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "icons": [
                    {
                      "name": "micro",
                      "label": "Micro"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/block-types": {
      "get": {
        "tags": [
          "Reference values"
        ],
        "operationId": "listBlockTypes",
        "summary": "List block types",
        "description": "Block types available to this account.",
        "responses": {
          "200": {
            "description": "Block types.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "types": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "link",
                          "heading",
                          "text",
                          "video",
                          "latest-video",
                          "email-signup",
                          "proof",
                          "product",
                          "tip",
                          "booking",
                          "music",
                          "faq",
                          "contact-card",
                          "location",
                          "gallery",
                          "contact-form",
                          "feed"
                        ]
                      }
                    }
                  }
                },
                "example": {
                  "types": [
                    "link",
                    "heading",
                    "text",
                    "video",
                    "latest-video",
                    "email-signup",
                    "proof",
                    "product",
                    "tip",
                    "booking",
                    "music",
                    "faq",
                    "contact-card",
                    "location",
                    "gallery",
                    "contact-form",
                    "feed"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/themes": {
      "get": {
        "tags": [
          "Reference values"
        ],
        "operationId": "listThemes",
        "summary": "List themes",
        "responses": {
          "200": {
            "description": "Themes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "themes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Theme"
                      }
                    }
                  }
                },
                "example": {
                  "themes": [
                    {
                      "id": "nuit-peche",
                      "name": "Nuit et pêche",
                      "mood": "Understated",
                      "family": "dark"
                    },
                    {
                      "id": "onyx",
                      "name": "Onyx et or",
                      "mood": "Luxury",
                      "family": "premium"
                    },
                    {
                      "id": "champagne",
                      "name": "Champagne",
                      "mood": "Editorial",
                      "family": "premium"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid_api_key",
                    "message": "Invalid or revoked API key."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Read-only key, plan without API access, terms of service not accepted, or account suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "plan_required",
                    "message": "The API and MCP server require the Pro plan. The key is reactivated when the account is back on Pro."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded: 120 requests per minute, 5,000 per day per key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Rate limit exceeded for this API key. Retry in one minute."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "cle": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "hopla_…",
        "description": "API key, created in Réglages (Settings), \"API et MCP\", on app.hopla.page."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable error code, for programmatic handling."
              },
              "message": {
                "type": "string",
                "description": "Human-readable message, in English."
              }
            }
          }
        }
      },
      "Page": {
        "type": "object",
        "properties": {
          "page": {
            "type": "object",
            "properties": {
              "username": {
                "type": "string",
                "description": "Page username."
              },
              "url": {
                "type": "string",
                "description": "Public page URL."
              },
              "plan": {
                "type": "string",
                "enum": [
                  "free",
                  "starter",
                  "pro",
                  "premium"
                ],
                "description": "Plan (gratuit = free)."
              }
            }
          },
          "settings": {
            "$ref": "#/components/schemas/Settings"
          },
          "blocks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Block"
            }
          }
        }
      },
      "SocialLink": {
        "type": "object",
        "required": [
          "network",
          "url"
        ],
        "properties": {
          "network": {
            "type": "string",
            "enum": [
              "youtube",
              "linkedin",
              "instagram",
              "tiktok",
              "x",
              "github",
              "facebook",
              "threads",
              "twitch",
              "spotify",
              "email",
              "site",
              "whatsapp",
              "telegram",
              "discord",
              "pinterest",
              "snapchat",
              "bluesky",
              "mastodon",
              "reddit",
              "substack",
              "medium",
              "patreon",
              "soundcloud",
              "deezer",
              "applemusic",
              "applepodcasts",
              "bandcamp",
              "vimeo",
              "behance",
              "dribbble",
              "malt",
              "etsy"
            ]
          },
          "url": {
            "type": "string",
            "description": "Profile URL, or a phone number for WhatsApp."
          }
        }
      },
      "Settings": {
        "type": "object",
        "description": "Page settings.",
        "properties": {
          "profile": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "description": "Name at the top of the page."
              },
              "bio": {
                "type": "string",
                "description": "Bio below the name."
              },
              "avatar": {
                "type": "string",
                "description": "Profile photo https URL."
              },
              "badge": {
                "type": "boolean",
                "description": "Verified badge, set by Hopla."
              },
              "photo": {
                "type": "string",
                "enum": [
                  "round",
                  "large"
                ],
                "description": "Profile photo layout: round (ronde) or large (grande, Starter)."
              }
            }
          },
          "socialLinks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SocialLink"
            },
            "maxItems": 20
          },
          "socialLinksPosition": {
            "type": "string",
            "enum": [
              "top",
              "bottom"
            ],
            "description": "Social icons position: top (haut) or bottom (bas)."
          },
          "theme": {
            "type": "string",
            "enum": [
              "nuit-peche",
              "onyx",
              "champagne",
              "neige",
              "foret",
              "klein",
              "brutal",
              "aurore",
              "coucher",
              "arcade",
              "bauhaus",
              "riso",
              "galaxie",
              "lampe",
              "skate",
              "sauge",
              "lin",
              "lilas",
              "marine",
              "argile",
              "mangue",
              "lagon",
              "cerise",
              "memphis"
            ]
          },
          "colors": {
            "type": "object",
            "description": "Custom colors as #rrggbb; empty: theme default (Starter).",
            "properties": {
              "background": {
                "type": "string",
                "description": "Background."
              },
              "buttons": {
                "type": "string",
                "description": "Buttons."
              },
              "accent": {
                "type": "string"
              }
            }
          },
          "button": {
            "type": "object",
            "properties": {
              "style": {
                "type": "string",
                "enum": [
                  "solid",
                  "outline",
                  "soft",
                  "raised",
                  "glass"
                ]
              },
              "shape": {
                "type": "string",
                "enum": [
                  "pill",
                  "rounded",
                  "square"
                ],
                "description": "Shape."
              },
              "shadow": {
                "type": "string",
                "enum": [
                  "none",
                  "subtle",
                  "strong"
                ],
                "description": "Shadow."
              }
            }
          },
          "seo": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "indexable": {
                "type": "boolean"
              }
            }
          },
          "shareButton": {
            "type": "boolean",
            "description": "Show the Share button at the top of the page."
          },
          "formal": {
            "type": "boolean",
            "description": "Page copy uses the formal \"vous\" form."
          },
          "animation": {
            "type": "string",
            "enum": [
              "none",
              "shine",
              "orbit",
              "glow",
              "wave",
              "breathe",
              "bounce",
              "shake"
            ],
            "description": "Animation of featured blocks."
          },
          "redirect": {
            "type": "object",
            "description": "Redirect the whole page to a URL until `endsAt`; empty `url` to stop (Starter).",
            "properties": {
              "url": {
                "type": "string"
              },
              "endsAt": {
                "type": "string"
              }
            }
          }
        },
        "additionalProperties": true,
        "example": {
          "profile": {
            "name": "Camille Durand",
            "bio": "Coach sportive à Lyon",
            "avatar": "https://hopla.page/media/k3v9.webp",
            "badge": false,
            "photo": "round"
          },
          "socialLinks": [
            {
              "network": "instagram",
              "url": "https://instagram.com/camille"
            },
            {
              "network": "youtube",
              "url": "https://youtube.com/@camille"
            }
          ],
          "socialLinksPosition": "top",
          "theme": "onyx",
          "colors": {
            "background": "",
            "buttons": "",
            "accent": ""
          },
          "button": {
            "style": "solid",
            "shape": "pill",
            "shadow": "subtle"
          },
          "seo": {
            "title": "",
            "description": "",
            "indexable": true
          },
          "shareButton": true,
          "openInApps": true,
          "weeklySummary": true,
          "formal": false,
          "animation": "shine",
          "networkViews": [],
          "desktopLayout": "column",
          "utm": false,
          "redirect": {
            "url": "",
            "endsAt": ""
          },
          "font": "",
          "backgroundImage": "",
          "domain": ""
        }
      },
      "SettingsUpdate": {
        "description": "Settings to change. Omitted settings are unchanged.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Settings"
          }
        ]
      },
      "Block": {
        "type": "object",
        "description": "A page block.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Block id, assigned by Hopla.",
            "readOnly": true
          },
          "type": {
            "type": "string",
            "enum": [
              "link",
              "heading",
              "text",
              "video",
              "latest-video",
              "email-signup",
              "proof",
              "product",
              "tip",
              "booking",
              "music",
              "faq",
              "contact-card",
              "location",
              "gallery",
              "contact-form",
              "feed"
            ],
            "description": "Block type. Immutable after creation."
          },
          "title": {
            "type": "string",
            "description": "Title: button label, section heading or paragraph text.",
            "example": "Mon nouveau podcast"
          },
          "url": {
            "type": "string",
            "description": "Target URL: https://…, mailto: or tel:.",
            "example": "https://example.com/podcast"
          },
          "subtitle": {
            "type": "string",
            "description": "Subtitle, one line below the title."
          },
          "description": {
            "type": "string",
            "description": "Longer text below the title of a banner or card."
          },
          "icon": {
            "type": "string",
            "description": "Icon or social network name: youtube, instagram, fusee…",
            "example": "micro"
          },
          "image": {
            "type": "string",
            "description": "Thumbnail https URL."
          },
          "visible": {
            "type": "boolean",
            "description": "Visible on the page. false keeps the block hidden in the admin panel."
          },
          "featured": {
            "type": "boolean",
            "description": "Featured (highlighted) block."
          },
          "format": {
            "type": "string",
            "enum": [
              "button",
              "banner",
              "card"
            ],
            "description": "Layout of a featured block: button, banner or card."
          },
          "tint": {
            "type": "string",
            "enum": [
              "accent",
              "theme"
            ],
            "description": "Color of a featured block: theme accent, or same as other buttons."
          },
          "animation": {
            "type": "string",
            "enum": [
              "auto",
              "none",
              "shine",
              "orbit",
              "glow",
              "wave",
              "breathe",
              "bounce",
              "shake"
            ],
            "description": "Animation of a featured block; auto follows the page settings."
          },
          "startsAt": {
            "type": "string",
            "description": "ISO 8601 date when the block starts showing; empty: immediately. Requires Starter.",
            "example": ""
          },
          "endsAt": {
            "type": "string",
            "description": "ISO 8601 date when the block stops showing; empty: never. Requires Starter.",
            "example": ""
          },
          "countdown": {
            "type": "boolean",
            "description": "Show a countdown before the start date instead of hiding the block."
          },
          "price": {
            "type": "string",
            "description": "Product price, as displayed: \"29 €\", \"Dès 12 €\"."
          },
          "amounts": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "For tip blocks: suggested amounts, in euros.",
            "example": [
              5,
              10,
              20
            ]
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Question"
            },
            "description": "FAQ items, in order. 20 maximum."
          },
          "proof": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Proof"
              },
              {
                "type": "null"
              }
            ],
            "description": "For proof blocks: a rating, a counter or a testimonial."
          },
          "partnership": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Partnership"
              },
              {
                "type": "null"
              }
            ],
            "description": "Affiliate link: disclosure label (always displayed) and promo code."
          },
          "address": {
            "type": "string",
            "description": "Postal address of a location or contact card."
          },
          "sensitive": {
            "type": "boolean",
            "description": "Adult content (18+): visitors must confirm before opening the link."
          },
          "layout": {
            "type": "string",
            "enum": [
              "list",
              "grid"
            ],
            "description": "For section headings: display the following links as a list or a grid."
          }
        },
        "additionalProperties": true,
        "example": {
          "id": "a1b2c3d4e5f6",
          "type": "link",
          "title": "Mon nouveau podcast",
          "url": "https://example.com/podcast",
          "subtitle": "",
          "description": "",
          "icon": "micro",
          "image": "",
          "visible": true,
          "indexable": true,
          "featured": false,
          "format": "banner",
          "tint": "accent",
          "animation": "auto",
          "startsAt": "",
          "endsAt": "",
          "countdown": false,
          "schedule": null,
          "audience": null,
          "hiddenFor": [],
          "askFirstName": false,
          "access": null,
          "proof": null,
          "price": "",
          "amounts": [],
          "partnership": null,
          "questions": [],
          "images": [],
          "contact": null,
          "address": "",
          "sensitive": false,
          "layout": "list",
          "countries": null,
          "gift": null
        }
      },
      "BlockCreate": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "link",
              "heading",
              "text",
              "video",
              "latest-video",
              "email-signup",
              "proof",
              "product",
              "tip",
              "booking",
              "music",
              "faq",
              "contact-card",
              "location",
              "gallery",
              "contact-form",
              "feed"
            ],
            "default": "link"
          },
          "title": {
            "type": "string",
            "description": "Title: button label, section heading or paragraph text.",
            "example": "Mon nouveau podcast"
          },
          "url": {
            "type": "string",
            "description": "Target URL: https://…, mailto: or tel:.",
            "example": "https://example.com/podcast"
          },
          "subtitle": {
            "type": "string",
            "description": "Subtitle, one line below the title."
          },
          "description": {
            "type": "string",
            "description": "Longer text below the title of a banner or card."
          },
          "icon": {
            "type": "string",
            "description": "Icon or social network name: youtube, instagram, fusee…",
            "example": "micro"
          },
          "image": {
            "type": "string",
            "description": "Thumbnail https URL."
          },
          "visible": {
            "type": "boolean",
            "description": "Visible on the page. false keeps the block hidden in the admin panel."
          },
          "featured": {
            "type": "boolean",
            "description": "Featured (highlighted) block."
          },
          "format": {
            "type": "string",
            "enum": [
              "button",
              "banner",
              "card"
            ],
            "description": "Layout of a featured block: button, banner or card."
          },
          "tint": {
            "type": "string",
            "enum": [
              "accent",
              "theme"
            ],
            "description": "Color of a featured block: theme accent, or same as other buttons."
          },
          "animation": {
            "type": "string",
            "enum": [
              "auto",
              "none",
              "shine",
              "orbit",
              "glow",
              "wave",
              "breathe",
              "bounce",
              "shake"
            ],
            "description": "Animation of a featured block; auto follows the page settings."
          },
          "startsAt": {
            "type": "string",
            "description": "ISO 8601 date when the block starts showing; empty: immediately. Requires Starter.",
            "example": ""
          },
          "endsAt": {
            "type": "string",
            "description": "ISO 8601 date when the block stops showing; empty: never. Requires Starter.",
            "example": ""
          },
          "countdown": {
            "type": "boolean",
            "description": "Show a countdown before the start date instead of hiding the block."
          },
          "price": {
            "type": "string",
            "description": "Product price, as displayed: \"29 €\", \"Dès 12 €\"."
          },
          "amounts": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "For tip blocks: suggested amounts, in euros.",
            "example": [
              5,
              10,
              20
            ]
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Question"
            },
            "description": "FAQ items, in order. 20 maximum."
          },
          "proof": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Proof"
              },
              {
                "type": "null"
              }
            ],
            "description": "For proof blocks: a rating, a counter or a testimonial."
          },
          "partnership": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Partnership"
              },
              {
                "type": "null"
              }
            ],
            "description": "Affiliate link: disclosure label (always displayed) and promo code."
          },
          "address": {
            "type": "string",
            "description": "Postal address of a location or contact card."
          },
          "sensitive": {
            "type": "boolean",
            "description": "Adult content (18+): visitors must confirm before opening the link."
          },
          "layout": {
            "type": "string",
            "enum": [
              "list",
              "grid"
            ],
            "description": "For section headings: display the following links as a list or a grid."
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "description": "Zero-based position, 0 = top (default)."
          }
        }
      },
      "BlockUpdate": {
        "type": "object",
        "description": "Fields to change.",
        "properties": {
          "title": {
            "type": "string",
            "description": "Title: button label, section heading or paragraph text.",
            "example": "Mon nouveau podcast"
          },
          "url": {
            "type": "string",
            "description": "Target URL: https://…, mailto: or tel:.",
            "example": "https://example.com/podcast"
          },
          "subtitle": {
            "type": "string",
            "description": "Subtitle, one line below the title."
          },
          "description": {
            "type": "string",
            "description": "Longer text below the title of a banner or card."
          },
          "icon": {
            "type": "string",
            "description": "Icon or social network name: youtube, instagram, fusee…",
            "example": "micro"
          },
          "image": {
            "type": "string",
            "description": "Thumbnail https URL."
          },
          "visible": {
            "type": "boolean",
            "description": "Visible on the page. false keeps the block hidden in the admin panel."
          },
          "featured": {
            "type": "boolean",
            "description": "Featured (highlighted) block."
          },
          "format": {
            "type": "string",
            "enum": [
              "button",
              "banner",
              "card"
            ],
            "description": "Layout of a featured block: button, banner or card."
          },
          "tint": {
            "type": "string",
            "enum": [
              "accent",
              "theme"
            ],
            "description": "Color of a featured block: theme accent, or same as other buttons."
          },
          "animation": {
            "type": "string",
            "enum": [
              "auto",
              "none",
              "shine",
              "orbit",
              "glow",
              "wave",
              "breathe",
              "bounce",
              "shake"
            ],
            "description": "Animation of a featured block; auto follows the page settings."
          },
          "startsAt": {
            "type": "string",
            "description": "ISO 8601 date when the block starts showing; empty: immediately. Requires Starter.",
            "example": ""
          },
          "endsAt": {
            "type": "string",
            "description": "ISO 8601 date when the block stops showing; empty: never. Requires Starter.",
            "example": ""
          },
          "countdown": {
            "type": "boolean",
            "description": "Show a countdown before the start date instead of hiding the block."
          },
          "price": {
            "type": "string",
            "description": "Product price, as displayed: \"29 €\", \"Dès 12 €\"."
          },
          "amounts": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "For tip blocks: suggested amounts, in euros.",
            "example": [
              5,
              10,
              20
            ]
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Question"
            },
            "description": "FAQ items, in order. 20 maximum."
          },
          "proof": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Proof"
              },
              {
                "type": "null"
              }
            ],
            "description": "For proof blocks: a rating, a counter or a testimonial."
          },
          "partnership": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Partnership"
              },
              {
                "type": "null"
              }
            ],
            "description": "Affiliate link: disclosure label (always displayed) and promo code."
          },
          "address": {
            "type": "string",
            "description": "Postal address of a location or contact card."
          },
          "sensitive": {
            "type": "boolean",
            "description": "Adult content (18+): visitors must confirm before opening the link."
          },
          "layout": {
            "type": "string",
            "enum": [
              "list",
              "grid"
            ],
            "description": "For section headings: display the following links as a list or a grid."
          }
        }
      },
      "Subscriber": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "firstName": {
            "type": "string",
            "description": "First name."
          },
          "subscribedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Signup date."
          },
          "source": {
            "type": "string",
            "description": "Referrer: instagram, direct, qr…"
          },
          "synced": {
            "type": "boolean",
            "description": "Already synced to the connected newsletter."
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "username": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Public page URL."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "plan": {
            "type": "string",
            "enum": [
              "free",
              "starter",
              "pro",
              "premium"
            ],
            "description": "Effective plan, trial included."
          },
          "trial": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "until": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Trial, with its end date."
          },
          "subscription": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "plan": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  },
                  "period": {
                    "type": "string"
                  },
                  "periodEnd": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Paid subscription."
          },
          "complimentaryPlan": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "plan": {
                    "type": "string"
                  },
                  "until": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Complimentary plan."
          },
          "features": {
            "type": "object",
            "description": "Gated features.",
            "properties": {
              "noBranding": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "unlimitedBlocks": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "storefront": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "customColors": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "scheduling": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "reviews": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "analytics": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "contactForm": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "gallery": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "utm": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "redirect": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "backgroundImage": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "largeProfilePhoto": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "gridLayout": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "timeSchedule": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "drops": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "emailList": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "countryTargeting": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "leadMagnet": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "feed": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              },
              "api": {
                "type": "object",
                "properties": {
                  "included": {
                    "type": "boolean"
                  },
                  "plan": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "analyticsRetentionDays": {
            "type": "integer",
            "description": "Analytics retention of the current plan, in days.",
            "example": 365
          }
        }
      },
      "Question": {
        "type": "object",
        "properties": {
          "question": {
            "type": "string"
          },
          "answer": {
            "type": "string",
            "description": "Answer."
          }
        }
      },
      "Proof": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "rating",
              "counter",
              "testimonial"
            ]
          },
          "rating": {
            "type": "number",
            "description": "Rating out of 5.",
            "example": 4.8
          },
          "count": {
            "type": "number",
            "description": "Review count, or the displayed number.",
            "example": 120
          },
          "source": {
            "type": "string",
            "example": "Google"
          }
        }
      },
      "Partnership": {
        "type": "object",
        "properties": {
          "disclosure": {
            "type": "string",
            "enum": [
              "advertising",
              "collaboration"
            ],
            "description": "Disclosure label."
          },
          "code": {
            "type": "string",
            "description": "Promo code; empty if none."
          }
        }
      },
      "Theme": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "mood": {
            "type": "string",
            "description": "Mood."
          },
          "family": {
            "type": "string",
            "description": "Family."
          }
        }
      },
      "Analytics": {
        "type": "object",
        "properties": {
          "days": {
            "type": "integer"
          },
          "views": {
            "type": "integer"
          },
          "visitors": {
            "type": "integer"
          },
          "clicks": {
            "type": "integer"
          },
          "daily": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "views": {
                  "type": "integer"
                },
                "visitors": {
                  "type": "integer"
                },
                "clicks": {
                  "type": "integer"
                }
              }
            }
          },
          "blocks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "clicks": {
                  "type": "integer"
                },
                "previous": {
                  "type": "integer"
                }
              }
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "views": {
                  "type": "integer"
                },
                "previous": {
                  "type": "integer"
                }
              }
            }
          },
          "devices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "views": {
                  "type": "integer"
                }
              }
            }
          },
          "countries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "example": "FR"
                },
                "views": {
                  "type": "integer"
                },
                "previous": {
                  "type": "integer"
                }
              }
            }
          },
          "previousPeriod": {
            "type": "object",
            "description": "Same totals for the previous period.",
            "properties": {
              "views": {
                "type": "integer"
              },
              "visitors": {
                "type": "integer"
              },
              "clicks": {
                "type": "integer"
              }
            }
          }
        },
        "example": {
          "days": 7,
          "views": 1284,
          "visitors": 902,
          "clicks": 311,
          "daily": [
            {
              "date": "2026-10-22",
              "views": 170,
              "visitors": 121,
              "clicks": 40
            }
          ],
          "blocks": [
            {
              "id": "a1b2c3d4e5f6",
              "title": "Mon nouveau podcast",
              "clicks": 96,
              "previous": 71
            }
          ],
          "sources": [
            {
              "name": "instagram",
              "views": 640,
              "previous": 512
            }
          ],
          "devices": [
            {
              "name": "mobile",
              "views": 1101
            }
          ],
          "countries": [
            {
              "code": "FR",
              "views": 1050,
              "previous": 930
            }
          ],
          "previousPeriod": {
            "views": 1120,
            "visitors": 801,
            "clicks": 260
          }
        }
      }
    }
  }
}
