{
  "openapi": "3.1.0",
  "info": {
    "title": "API do Praeko",
    "version": "1",
    "description": "API pública do Praeko (publicador de vídeos curtos para YouTube, Instagram e TikTok). Gerada da tabela de rotas do contrato; identificadores em inglês, textos em português. Mande sempre `Authorization: Bearer praeko_…` e `X-Praeko-Contract: en`."
  },
  "servers": [
    {
      "url": "https://praeko.app"
    }
  ],
  "tags": [
    {
      "name": "Saúde"
    },
    {
      "name": "Conexões"
    },
    {
      "name": "Mídia"
    },
    {
      "name": "Canais"
    },
    {
      "name": "TikTok"
    },
    {
      "name": "Posts"
    },
    {
      "name": "Calendário"
    },
    {
      "name": "Insights"
    },
    {
      "name": "Conteúdo"
    },
    {
      "name": "YouTube"
    },
    {
      "name": "Instagram"
    },
    {
      "name": "Comentários"
    },
    {
      "name": "Chaves de API"
    },
    {
      "name": "Uso e log"
    },
    {
      "name": "Preferências"
    },
    {
      "name": "Avisos"
    },
    {
      "name": "Webhooks"
    },
    {
      "name": "Ideias"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "health",
        "tags": [
          "Saúde"
        ],
        "summary": "Saúde do serviço",
        "description": "Confere se o serviço está no ar. Não precisa de chave.",
        "x-praeko-scope": "public",
        "parameters": [],
        "security": [],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "service": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    },
                    "time": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "ok",
                    "service",
                    "version",
                    "time"
                  ]
                },
                "example": {
                  "ok": true,
                  "service": "praeko",
                  "version": "0.1.0",
                  "time": "2026-11-07T14:02:11.482Z"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "operationId": "health.v1",
        "tags": [
          "Saúde"
        ],
        "summary": "Saúde do serviço (caminho versionado)",
        "description": "O mesmo que `/health`, no caminho versionado. Não precisa de chave.",
        "x-praeko-scope": "public",
        "parameters": [],
        "security": [],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "service": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "ok",
                    "service",
                    "version"
                  ]
                },
                "example": {
                  "ok": true,
                  "service": "praeko",
                  "version": "0.1.0"
                }
              }
            }
          }
        }
      }
    },
    "/connections/{network}/callback": {
      "get": {
        "operationId": "connections.callback",
        "tags": [
          "Conexões"
        ],
        "summary": "Retorno do OAuth",
        "description": "Endereço para onde YouTube, TikTok e Meta devolvem o navegador depois do login (página HTML). Os endereços já cadastrados continuam valendo.",
        "x-praeko-scope": "public",
        "parameters": [
          {
            "name": "network",
            "in": "path",
            "required": true,
            "description": "Rede: youtube, instagram ou tiktok.",
            "schema": {
              "type": "string",
              "enum": [
                "youtube",
                "instagram",
                "tiktok"
              ]
            },
            "example": "youtube"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Sucesso. Uma página HTML curta (\"Pronto\", \"Autorização negada\", \"Link vencido\"…) para o navegador de quem autorizou. Não é JSON: o resultado também aparece em `GET /v1/channels`.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "x-praeko-lido-do-codigo": true
      }
    },
    "/v1/m/{id}/{token}/{name}": {
      "get": {
        "operationId": "media.public",
        "tags": [
          "Mídia"
        ],
        "summary": "Mídia pública por token",
        "description": "Mídia pública acessada por um link com token (aceita Range). Também responde em `/m/…`. É o endereço que Meta e TikTok baixam.",
        "x-praeko-scope": "public",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "Token do link público da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "k7Qm2vXz9Rt4Lp8Wc3Nd6Hy1Bs5Fa0J"
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "Nome do arquivo.",
            "schema": {
              "type": "string"
            },
            "example": "video.mp4"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "O arquivo inteiro. Os bytes do arquivo com `Cache-Control: public, max-age=3600` e `X-Robots-Tag: noindex, nofollow`. Token errado ou mídia que não existe: 404 de texto simples; `Range` fora do arquivo: 416.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Um pedaço, quando a requisição traz `Range: bytes=…`.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      },
      "head": {
        "operationId": "media.public.head",
        "tags": [
          "Mídia"
        ],
        "summary": "Mídia pública por token",
        "description": "Igual ao GET, mas só devolve os cabeçalhos.",
        "x-praeko-scope": "public",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "Token do link público da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "k7Qm2vXz9Rt4Lp8Wc3Nd6Hy1Bs5Fa0J"
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "Nome do arquivo.",
            "schema": {
              "type": "string"
            },
            "example": "video.mp4"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Só cabeçalhos do arquivo inteiro. Os mesmos cabeçalhos do GET, sem corpo."
          },
          "206": {
            "description": "Só cabeçalhos do pedaço pedido por `Range`."
          }
        }
      }
    },
    "/v1/connections/{network}/start": {
      "get": {
        "operationId": "connections.start",
        "tags": [
          "Conexões"
        ],
        "summary": "Iniciar conexão de rede",
        "description": "Inicia o login OAuth de youtube, tiktok ou meta (Instagram) e devolve o endereço de autorização. Outra conta da mesma rede vira outro canal; a mesma conta é reconectada. Em `meta` valem `replace=true` (troca o token de uma conexão existente), `add=true` (conecta OUTRA conta do Instagram como novo canal), `instagram=<usuário>`, `instagram_id=<id|current>` e `probe=true` (só relata, não grava nada).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "network",
            "in": "path",
            "required": true,
            "description": "Rede: youtube, instagram ou tiktok.",
            "schema": {
              "type": "string",
              "enum": [
                "youtube",
                "instagram",
                "tiktok"
              ]
            },
            "example": "youtube"
          },
          {
            "name": "replace",
            "in": "query",
            "required": false,
            "description": "Só `meta`: `true` troca o token de uma conexão existente.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "add",
            "in": "query",
            "required": false,
            "description": "Só `meta`: `true` conecta OUTRA conta como novo canal.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "probe",
            "in": "query",
            "required": false,
            "description": "Só `meta`: `true` só relata, não grava.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. A forma da resposta muda por rede e opção (`meta` com `probe=true` só informa, sem guardar nada); `url` é o que se abre no navegador.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    },
                    "redirect": {
                      "type": "string"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "warning": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "url"
                  ]
                },
                "example": {
                  "url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=1234567890-abc.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fpraeko.app%2Fconexoes%2Fyoutube%2Fretorno&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.upload+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.force-ssl+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyt-analytics.readonly&code_challenge=D0BtvxL1QkepqaFLJJS5wrahCRBAulz64NHqFygjhnE&code_challenge_method=S256&state=ISfuXJZxVw4VCiEfWGEhY96pfpYA0YEo&access_type=offline&prompt=consent",
                  "redirect": "https://praeko.app/conexoes/youtube/retorno",
                  "expiresAt": "2026-11-07T14:12:11.482Z",
                  "warning": "abra a url no navegador, autorize e volte; o retorno cai neste serviço"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/connections/{network}": {
      "delete": {
        "operationId": "connections.disconnect",
        "tags": [
          "Conexões"
        ],
        "summary": "Desconectar rede",
        "description": "Desconecta uma conta de youtube, tiktok ou instagram (só o token da Meta; o canal do Buffer nunca é mexido). `channel` escolhe a conta; sem ele, vale o canal padrão. As outras contas, o canal e o histórico ficam. Se era o padrão, outra conta conectada assume.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "network",
            "in": "path",
            "required": true,
            "description": "Rede: youtube, instagram ou tiktok.",
            "schema": {
              "type": "string",
              "enum": [
                "youtube",
                "instagram",
                "tiktok"
              ]
            },
            "example": "youtube"
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "network": {
                      "type": "string"
                    },
                    "disconnected": {
                      "type": "boolean"
                    },
                    "channel": {
                      "type": "string"
                    },
                    "revokedAtGoogle": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "network",
                    "disconnected",
                    "channel"
                  ]
                },
                "example": {
                  "network": "youtube",
                  "disconnected": true,
                  "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                  "revokedAtGoogle": true
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/connections/instagram/token": {
      "put": {
        "operationId": "connections.instagramToken",
        "tags": [
          "Conexões"
        ],
        "summary": "Token do Instagram",
        "description": "Guarda o token da API da Meta para o Instagram (cifrado). O token é conferido na Meta antes de gravar.",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "O token do exemplo é só um molde: use o seu. O servidor confere o token na Meta antes de guardar.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "Token de acesso da API da Meta (Instagram).",
                    "minLength": 20,
                    "maxLength": 4000
                  },
                  "userId": {
                    "type": "string",
                    "description": "Id numérico da conta do Instagram.",
                    "maxLength": 40,
                    "pattern": "^\\d+$"
                  },
                  "host": {
                    "type": "string",
                    "enum": [
                      "https://graph.facebook.com",
                      "https://graph.instagram.com"
                    ],
                    "description": "Servidor da Meta.",
                    "default": "https://graph.facebook.com"
                  },
                  "username": {
                    "type": "string",
                    "description": "Usuário, usado só se a Meta não devolver o nome."
                  },
                  "type": {
                    "type": "string",
                    "description": "Tipo da conta.",
                    "default": "BUSINESS"
                  }
                },
                "required": [
                  "token",
                  "userId"
                ]
              },
              "example": {
                "token": "IGAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                "userId": "17841400000000000",
                "host": "https://graph.instagram.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connected": {
                      "type": "boolean"
                    },
                    "username": {
                      "type": "string"
                    },
                    "followers": {
                      "type": "integer"
                    },
                    "mediaItems": {
                      "type": "integer"
                    },
                    "host": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "connected",
                    "username",
                    "followers",
                    "mediaItems",
                    "host"
                  ]
                },
                "example": {
                  "connected": true,
                  "username": "dev.ocioso",
                  "followers": 1842,
                  "mediaItems": 37,
                  "host": "https://graph.instagram.com"
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: Token, `userId` ou `host` inválidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "502": {
            "description": "`instagram_error`: A Meta recusou o token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/channels": {
      "get": {
        "operationId": "channels.list",
        "tags": [
          "Canais"
        ],
        "summary": "Listar canais",
        "description": "Um item por canal conectado (várias contas da mesma rede viram vários itens; `default` marca a usada quando não se informa `channel`), mais um item \"não conectado\" para a rede sem conta. Traz o estado e a quantidade de horários.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Um item por conta conectada (várias contas de uma rede = vários itens) e um item \"não conectado\" para a rede sem conta.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "channels": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "network": {
                            "type": "string"
                          },
                          "supported": {
                            "type": "boolean"
                          },
                          "id": {
                            "type": "string"
                          },
                          "connected": {
                            "type": "boolean"
                          },
                          "detail": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "timezone": {
                            "type": "string"
                          },
                          "slots": {
                            "type": "integer"
                          },
                          "avatar": {
                            "type": "string"
                          },
                          "default": {
                            "type": "boolean"
                          },
                          "meta": {
                            "type": "object",
                            "properties": {
                              "connected": {
                                "type": "boolean"
                              },
                              "username": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "connected",
                              "username"
                            ]
                          }
                        },
                        "required": [
                          "network",
                          "supported",
                          "connected",
                          "detail"
                        ]
                      }
                    }
                  },
                  "required": [
                    "channels"
                  ]
                },
                "example": {
                  "channels": [
                    {
                      "network": "youtube",
                      "supported": true,
                      "id": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "connected": true,
                      "detail": "Canal do Marcone",
                      "name": "Canal do Marcone",
                      "timezone": "America/Sao_Paulo",
                      "slots": 3,
                      "avatar": "/v1/channels/youtube/avatar",
                      "default": true
                    },
                    {
                      "network": "instagram",
                      "supported": true,
                      "id": "01KA3V1G4C7N9PXS2WQ6HM8TDZ",
                      "connected": true,
                      "detail": "via API da Meta",
                      "timezone": "America/Sao_Paulo",
                      "slots": 0,
                      "meta": {
                        "connected": true,
                        "username": "dev.ocioso"
                      },
                      "name": "dev.ocioso"
                    },
                    {
                      "network": "tiktok",
                      "supported": true,
                      "connected": false,
                      "detail": "não conectado (faltam os secrets TIKTOK_CLIENT_KEY e TIKTOK_CLIENT_SECRET)"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/channels/{channel}/default": {
      "put": {
        "operationId": "channels.default",
        "tags": [
          "Canais"
        ],
        "summary": "Canal padrão da rede",
        "description": "Torna esta conta a padrão da rede (usada quando uma rota de rede não traz `channel`); as outras contas da mesma rede perdem a marca. Dá 404 `channel_not_found` se não for um canal ativo deste workspace.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "description": "Id do canal (campo `id` de `GET /v1/channels`).",
            "schema": {
              "type": "string"
            },
            "example": "01KA3V0R5D8M2WQ7X4HT9NZ1BF"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "network": {
                      "type": "string"
                    },
                    "channel": {
                      "type": "string"
                    },
                    "default": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "network",
                    "channel",
                    "default"
                  ]
                },
                "example": {
                  "network": "youtube",
                  "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                  "default": true
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/channels/{channel}": {
      "delete": {
        "operationId": "channels.remove",
        "tags": [
          "Canais"
        ],
        "summary": "Remover canal",
        "description": "Tira da lista uma conta DESCONECTADA (o histórico fica). Recusa enquanto estiver conectada, com itens pendentes ou se for o canal do Instagram no Buffer.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "description": "Id do canal (campo `id` de `GET /v1/channels`).",
            "schema": {
              "type": "string"
            },
            "example": "01KA3V0R5D8M2WQ7X4HT9NZ1BF"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "network": {
                      "type": "string"
                    },
                    "channel": {
                      "type": "string"
                    },
                    "accountRemoved": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "network",
                    "channel",
                    "accountRemoved"
                  ]
                },
                "example": {
                  "network": "youtube",
                  "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                  "accountRemoved": true
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/channels/{network}/avatar": {
      "get": {
        "operationId": "channels.avatar",
        "tags": [
          "Canais"
        ],
        "summary": "Foto do canal",
        "description": "Imagem de perfil do canal (`channel` escolhe a conta).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "network",
            "in": "path",
            "required": true,
            "description": "Rede: youtube, instagram ou tiktok.",
            "schema": {
              "type": "string",
              "enum": [
                "youtube",
                "instagram",
                "tiktok"
              ]
            },
            "example": "youtube"
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. A imagem do avatar (`image/png` ou `image/jpeg`), com `Cache-Control: private, max-age=21600`. 404 `no_avatar` quando o canal não tem foto.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tiktok/creator": {
      "get": {
        "operationId": "tiktok.creator",
        "tags": [
          "TikTok"
        ],
        "summary": "Criador no TikTok",
        "description": "O que a conta do TikTok conectada aceita (`creator_info`: privacidades, duração máxima…). `channel` escolhe a conta.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Enquanto o app não passa na auditoria do TikTok, `allowedPrivacyLevels` traz só `SELF_ONLY`. A resposta fica em cache por 60 s.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connected": {
                      "type": "boolean"
                    },
                    "name": {
                      "type": "string"
                    },
                    "username": {
                      "type": "string"
                    },
                    "avatar": {
                      "type": "string"
                    },
                    "privacyLevels": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "allowedPrivacyLevels": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "appAudited": {
                      "type": "boolean"
                    },
                    "directAvailable": {
                      "type": "boolean"
                    },
                    "maxDurationSec": {
                      "type": "integer"
                    },
                    "commentsDisabled": {
                      "type": "boolean"
                    },
                    "duetDisabled": {
                      "type": "boolean"
                    },
                    "stitchDisabled": {
                      "type": "boolean"
                    },
                    "queriedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "connected",
                    "name",
                    "username",
                    "privacyLevels",
                    "allowedPrivacyLevels",
                    "appAudited",
                    "directAvailable",
                    "maxDurationSec",
                    "commentsDisabled",
                    "duetDisabled",
                    "stitchDisabled",
                    "queriedAt"
                  ]
                },
                "example": {
                  "connected": true,
                  "name": "Dev Ocioso",
                  "username": "devocioso",
                  "avatar": "/v1/channels/tiktok/avatar",
                  "privacyLevels": [
                    "SELF_ONLY",
                    "PUBLIC_TO_EVERYONE"
                  ],
                  "allowedPrivacyLevels": [
                    "SELF_ONLY"
                  ],
                  "appAudited": false,
                  "directAvailable": true,
                  "maxDurationSec": 600,
                  "commentsDisabled": false,
                  "duetDisabled": false,
                  "stitchDisabled": false,
                  "queriedAt": "2026-11-07T14:02:11.482Z"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "x-praeko-lido-do-codigo": true
      }
    },
    "/v1/channels/{network}/slots": {
      "get": {
        "operationId": "channels.slots.get",
        "tags": [
          "Canais"
        ],
        "summary": "Horários do canal",
        "description": "Horários semanais de publicação de um canal (`channel` escolhe a conta; sem ele, o padrão).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "network",
            "in": "path",
            "required": true,
            "description": "Rede: youtube, instagram ou tiktok.",
            "schema": {
              "type": "string",
              "enum": [
                "youtube",
                "instagram",
                "tiktok"
              ]
            },
            "example": "youtube"
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "network": {
                      "type": "string"
                    },
                    "timezone": {
                      "type": "string"
                    },
                    "slots": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "weekday": {
                            "type": "integer"
                          },
                          "day": {
                            "type": "string"
                          },
                          "time": {
                            "type": "string"
                          },
                          "active": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "weekday",
                          "day",
                          "time",
                          "active"
                        ]
                      }
                    }
                  },
                  "required": [
                    "network",
                    "timezone",
                    "slots"
                  ]
                },
                "example": {
                  "network": "youtube",
                  "timezone": "America/Sao_Paulo",
                  "slots": [
                    {
                      "weekday": 0,
                      "day": "sun",
                      "time": "22:00",
                      "active": true
                    },
                    {
                      "weekday": 1,
                      "day": "mon",
                      "time": "12:00",
                      "active": true
                    },
                    {
                      "weekday": 5,
                      "day": "fri",
                      "time": "17:30",
                      "active": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "channels.slots.put",
        "tags": [
          "Canais"
        ],
        "summary": "Salvar horários do canal",
        "description": "Troca todos os horários semanais de um canal e reorganiza o que está na fila (`channel` escolhe a conta).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "network",
            "in": "path",
            "required": true,
            "description": "Rede: youtube, instagram ou tiktok.",
            "schema": {
              "type": "string",
              "enum": [
                "youtube",
                "instagram",
                "tiktok"
              ]
            },
            "example": "youtube"
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "timezone": {
                    "type": "string",
                    "description": "Fuso horário IANA do canal. Sem ele, mantém o atual.",
                    "maxLength": 64
                  },
                  "slots": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    },
                    "maxItems": 100,
                    "description": "Os horários semanais (substituem TODOS os atuais; no máximo 100). Cada um é um texto `\"fri 17:30\"` ou um objeto `{ \"day\": \"fri\", \"time\": \"17:30\" }`. Dias: sun, mon, tue, wed, thu, fri, sat (ou `weekday` de 0 a 6). Hora: HH:MM de 00:00 a 23:59. Repetidos viram um só."
                  }
                },
                "required": [
                  "slots"
                ]
              },
              "example": {
                "timezone": "America/Sao_Paulo",
                "slots": [
                  "mon 12:00",
                  "fri 17:30",
                  {
                    "day": "sun",
                    "time": "22:00"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "network": {
                      "type": "string"
                    },
                    "timezone": {
                      "type": "string"
                    },
                    "slots": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "weekday": {
                            "type": "integer"
                          },
                          "day": {
                            "type": "string"
                          },
                          "time": {
                            "type": "string"
                          },
                          "active": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "weekday",
                          "day",
                          "time",
                          "active"
                        ]
                      }
                    },
                    "rescheduled": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "network",
                    "timezone",
                    "slots",
                    "rescheduled"
                  ]
                },
                "example": {
                  "network": "youtube",
                  "timezone": "America/Sao_Paulo",
                  "slots": [
                    {
                      "weekday": 0,
                      "day": "sun",
                      "time": "22:00",
                      "active": true
                    },
                    {
                      "weekday": 1,
                      "day": "mon",
                      "time": "12:00",
                      "active": true
                    },
                    {
                      "weekday": 5,
                      "day": "fri",
                      "time": "17:30",
                      "active": true
                    }
                  ],
                  "rescheduled": []
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`channel_not_found`: A rede não está conectada.\n`unknown_network`: Rede desconhecida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: Horário ou dia inválido, mais de 100 horários ou fuso desconhecido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/channels/{network}/followers": {
      "put": {
        "operationId": "channels.followers.put",
        "tags": [
          "Canais"
        ],
        "summary": "Registrar seguidores",
        "description": "Informa a contagem de seguidores à mão (só Instagram; `channel` escolhe a conta). Vira o registro do dia.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "network",
            "in": "path",
            "required": true,
            "description": "Rede: youtube, instagram ou tiktok.",
            "schema": {
              "type": "string",
              "enum": [
                "youtube",
                "instagram",
                "tiktok"
              ]
            },
            "example": "youtube"
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Só vale para `instagram` (no TikTok e no YouTube os seguidores já vêm da API; a rede errada dá 409 `followers_from_api`).",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "integer",
                    "description": "Quantidade de seguidores.",
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "day": {
                    "type": "string",
                    "description": "Dia do registro, `AAAA-MM-DD`, no máximo amanhã. Sem ele, hoje no fuso do canal.",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "value"
                ]
              },
              "example": {
                "value": 1842,
                "day": "2026-11-06"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "network": {
                      "type": "string"
                    },
                    "day": {
                      "type": "string"
                    },
                    "followers": {
                      "type": "integer"
                    },
                    "source": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "network",
                    "day",
                    "followers",
                    "source"
                  ]
                },
                "example": {
                  "network": "instagram",
                  "day": "2026-11-06",
                  "followers": 1842,
                  "source": "manual"
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`channel_not_found`: Canal não conectado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`followers_from_api`: A rede já traz os seguidores pela API (só `instagram` aceita valor manual).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `value` ou `day` inválidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "channels.followers.get",
        "tags": [
          "Canais"
        ],
        "summary": "Seguidores do canal",
        "description": "Os últimos 60 registros diários de seguidores (`channel` escolhe a conta).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "network",
            "in": "path",
            "required": true,
            "description": "Rede: youtube, instagram ou tiktok.",
            "schema": {
              "type": "string",
              "enum": [
                "youtube",
                "instagram",
                "tiktok"
              ]
            },
            "example": "youtube"
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Até 60 registros diários, do mais antigo ao mais novo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "network": {
                      "type": "string"
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "day": {
                            "type": "string"
                          },
                          "followers": {
                            "type": "integer"
                          },
                          "source": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "day",
                          "followers",
                          "source"
                        ]
                      }
                    }
                  },
                  "required": [
                    "network",
                    "records"
                  ]
                },
                "example": {
                  "network": "instagram",
                  "records": [
                    {
                      "day": "2026-11-05",
                      "followers": 1805,
                      "source": "manual"
                    },
                    {
                      "day": "2026-11-06",
                      "followers": 1842,
                      "source": "manual"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/media": {
      "post": {
        "operationId": "media.create",
        "tags": [
          "Mídia"
        ],
        "summary": "Criar mídia",
        "description": "Começa o envio de uma mídia (vídeo, capa ou imagem: a foto de um post de imagem, só JPEG). Com `simple: true` e até 50 MB, envie o arquivo todo de uma vez em `PUT /v1/media/:id/content`; senão, envie em partes e conclua com `POST /v1/media/:id/complete`.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "video",
                      "cover",
                      "image"
                    ],
                    "description": "Tipo da mídia: `video`, `cover` (capa) ou `image` (a foto de um post de imagem)."
                  },
                  "name": {
                    "type": "string",
                    "description": "Nome do arquivo (guardado com os caracteres seguros).",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "mime": {
                    "type": "string",
                    "enum": [
                      "video/mp4",
                      "video/quicktime",
                      "image/png",
                      "image/jpeg"
                    ],
                    "description": "Tipo do arquivo. Vídeo: `video/mp4` ou `video/quicktime`. Capa: `image/png` ou `image/jpeg`. Imagem: só `image/jpeg` (o Studio exporta JPEG sRGB; o servidor não converte)."
                  },
                  "size": {
                    "type": "integer",
                    "description": "Tamanho em bytes. Vídeo até 5 GiB; capa e imagem até 20 MiB (o Instagram aceita foto de até 8 MB: isso é conferido ao criar o post).",
                    "minimum": 1,
                    "maximum": 5368709120
                  },
                  "simple": {
                    "type": "boolean",
                    "description": "`true` envia o arquivo todo em um `PUT /v1/media/:id/content` (só até 50 MB). Sem ele, o envio é em partes.",
                    "default": false
                  },
                  "sha256": {
                    "type": "string",
                    "description": "Hash SHA-256 do arquivo, se quiser guardá-lo."
                  }
                },
                "required": [
                  "type",
                  "name",
                  "mime",
                  "size"
                ]
              },
              "example": {
                "type": "video",
                "name": "dia-06-o-teste-que-falhava.mp4",
                "mime": "video/mp4",
                "size": 48230114,
                "simple": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso. Com `simple: true` a resposta é só `id`, `state` e `uploadHint`. Sem ele (envio em partes) vêm também `minPartSize` (5242880), `maxPartSize` (99614720) e `suggestedPartSize` (16777216), em bytes, e o `uploadHint` aponta para `PUT /v1/media/:id/parts/<n>`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "state": {
                      "type": "string"
                    },
                    "uploadHint": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "state",
                    "uploadHint"
                  ]
                },
                "example": {
                  "id": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                  "state": "uploading",
                  "uploadHint": "PUT /v1/media/01KA3VQ7M2T9XE4HB8N5RZ6C0D/content"
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `type`, `mime`, `name` ou `size` inválidos.\n`file_too_large`: `simple: true` com mais de 50 MB.\n`invalid_image`: Mídia `image` cujo arquivo não é um JPEG (a conferência acontece no envio; no envio em partes, ao completar).\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "media.list",
        "tags": [
          "Mídia"
        ],
        "summary": "Listar mídias",
        "description": "Lista as mídias (as últimas 100). Vídeos antigos ganham duração, largura e altura na primeira leitura.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Filtra por tipo.",
            "schema": {
              "type": "string",
              "enum": [
                "video",
                "cover",
                "image"
              ]
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. As últimas 100, da mais nova para a mais antiga.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "media": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "mime": {
                            "type": "string"
                          },
                          "size": {
                            "type": "integer"
                          },
                          "state": {
                            "type": "string"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "type",
                          "name",
                          "mime",
                          "size",
                          "state",
                          "createdAt"
                        ]
                      }
                    }
                  },
                  "required": [
                    "media"
                  ]
                },
                "example": {
                  "media": [
                    {
                      "id": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                      "type": "video",
                      "name": "video.mp4",
                      "mime": "video/mp4",
                      "size": 18874368,
                      "state": "ready",
                      "createdAt": "2026-11-07T15:40:03.120Z"
                    },
                    {
                      "id": "01KA3VR1J5W8GD2KP7Y4FN9S3B",
                      "type": "cover",
                      "name": "capa.png",
                      "mime": "image/png",
                      "size": 412873,
                      "state": "ready",
                      "createdAt": "2026-11-07T14:02:11.482Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/media/{id}": {
      "get": {
        "operationId": "media.get",
        "tags": [
          "Mídia"
        ],
        "summary": "Ver mídia",
        "description": "Uma mídia (com `publicUrl` quando estiver pronta; vídeos trazem também `durationMs`, `width` e `height` quando o cabeçalho do arquivo diz; imagens trazem `width` e `height`).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Enquanto `state` é `uploading`, a resposta traz `parts` (`[{ n, size }]`, as partes já recebidas). Quando está `ready`, vem `publicUrl`; vídeos também trazem `durationMs`, `width` e `height` quando o contêiner informa.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "mime": {
                      "type": "string"
                    },
                    "size": {
                      "type": "integer"
                    },
                    "state": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "publicUrl": {
                      "type": "string"
                    },
                    "durationMs": {
                      "type": "integer"
                    },
                    "width": {
                      "type": "integer"
                    },
                    "height": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id",
                    "type",
                    "name",
                    "mime",
                    "size",
                    "state",
                    "createdAt"
                  ]
                },
                "example": {
                  "id": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                  "type": "video",
                  "name": "video.mp4",
                  "mime": "video/mp4",
                  "size": 18874368,
                  "state": "ready",
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "publicUrl": "https://praeko.app/v1/m/01KA3VQ7M2T9XE4HB8N5RZ6C0D/k7Qm2vXz9Rt4Lp8Wc3Nd6Hy1Bs5Fa0J/video.mp4",
                  "durationMs": 58240,
                  "width": 1080,
                  "height": 1920
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`media_not_found`: A mídia não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "media.delete",
        "tags": [
          "Mídia"
        ],
        "summary": "Apagar mídia",
        "description": "Remove uma mídia (recusa enquanto um post pendente a usa).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "removed": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "removed"
                  ]
                },
                "example": {
                  "removed": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`media_not_found`: A mídia não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`media_in_use`: Um post pendente usa a mídia.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/media/{id}/parts/{part}": {
      "put": {
        "operationId": "media.part",
        "tags": [
          "Mídia"
        ],
        "summary": "Enviar parte da mídia",
        "description": "Envia uma parte do arquivo (bytes crus no corpo). Reenviar a mesma parte é seguro. Toda parte, menos a última, precisa ter o mesmo tamanho (no mínimo 5 MiB).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
          },
          {
            "name": "part",
            "in": "path",
            "required": true,
            "description": "Número da parte, de 1 a 10000.",
            "schema": {
              "type": "string"
            },
            "example": "1"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Os bytes crus do arquivo.",
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso. Mande a mesma parte de novo para substituí-la.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "part": {
                      "type": "integer"
                    },
                    "size": {
                      "type": "integer"
                    },
                    "etag": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "part",
                    "size",
                    "etag"
                  ]
                },
                "example": {
                  "id": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                  "part": 1,
                  "size": 16777216,
                  "etag": "eb4fc1b20b020225bebfd48686d92517"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/media/{id}/content": {
      "put": {
        "operationId": "media.content.put",
        "tags": [
          "Mídia"
        ],
        "summary": "Enviar arquivo da mídia",
        "description": "Envia o arquivo inteiro de uma vez (mídia criada com `simple: true`, até 50 MB). O corpo são os bytes crus.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Os bytes crus do arquivo.",
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso. Envio em uma requisição só, para mídia criada com `simple: true`. Devolve a mídia já `ready`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "mime": {
                      "type": "string"
                    },
                    "size": {
                      "type": "integer"
                    },
                    "state": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "type",
                    "name",
                    "mime",
                    "size",
                    "state",
                    "createdAt"
                  ]
                },
                "example": {
                  "id": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                  "type": "video",
                  "name": "video.mp4",
                  "mime": "video/mp4",
                  "size": 18874368,
                  "state": "ready",
                  "createdAt": "2026-11-07T14:02:11.482Z"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "media.content.get",
        "tags": [
          "Mídia"
        ],
        "summary": "Baixar arquivo da mídia",
        "description": "Baixa o arquivo (com chave; aceita Range).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "O arquivo inteiro. Os bytes do arquivo, com `Content-Type`, `Content-Length` e `Accept-Ranges: bytes`. `Range` fora do arquivo dá 416.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Um pedaço, quando a requisição traz `Range: bytes=…` (a resposta traz `Content-Range`).",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "head": {
        "operationId": "media.content.head.head",
        "tags": [
          "Mídia"
        ],
        "summary": "Conferir arquivo da mídia",
        "description": "Igual ao GET, mas só devolve os cabeçalhos.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Só cabeçalhos do arquivo inteiro. Os mesmos cabeçalhos do GET (`Content-Type`, `Content-Length`, `Accept-Ranges`), sem corpo."
          },
          "206": {
            "description": "Só cabeçalhos do pedaço pedido por `Range`."
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/media/{id}/complete": {
      "post": {
        "operationId": "media.complete",
        "tags": [
          "Mídia"
        ],
        "summary": "Concluir envio da mídia",
        "description": "Junta as partes enviadas. Confere o tamanho combinado e a sequência 1..N (e, numa mídia `image`, que o arquivo é um JPEG). Chamar de novo é inofensivo.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da mídia.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Junta as partes na ordem. A mídia volta `ready`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "mime": {
                      "type": "string"
                    },
                    "size": {
                      "type": "integer"
                    },
                    "state": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "type",
                    "name",
                    "mime",
                    "size",
                    "state",
                    "createdAt"
                  ]
                },
                "example": {
                  "id": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                  "type": "video",
                  "name": "video.mp4",
                  "mime": "video/mp4",
                  "size": 18874368,
                  "state": "ready",
                  "createdAt": "2026-11-07T14:02:11.482Z"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`parts_missing`: Falta alguma parte da sequência.\n`size_mismatch`: O total enviado não bate com o `size` combinado.\n`part_too_small`: Uma parte (que não a última) tem menos de 5 MiB.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/posts": {
      "post": {
        "operationId": "posts.create",
        "tags": [
          "Posts"
        ],
        "summary": "Criar post",
        "description": "Cria um post com um item por rede (fila, horário fixo ou rascunho). Cada item pode levar `channel` (a conta daquela rede; sem ele, vale o canal padrão). Com `kind: \"image\"` é um post de foto única, só no Instagram (pelo Buffer ou direto pela Meta).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "O servidor devolve 422 `invalid_request` com a lista completa dos problemas em `details.errors`. A mídia precisa estar `ready` (completa). **Post de imagem (`kind: image`):** foto única no Instagram, pelo Buffer ou direto pela Meta. A foto é uma mídia `type: image` (JPEG); o Instagram aceita JPEG de até 8 MB com proporção de 4:5 a 1,91:1 (use 1080x1350). O item pode voltar com `warnings` (`code` estável): `alt_ignored` (o Buffer não tem texto alternativo), `image_small`, `image_will_be_resized`, `image_dimensions_unknown`. O post volta com `kind` e `media.images`.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "Título do post. Obrigatório, a não ser que `texts.youtube.title` exista.",
                    "maxLength": 200
                  },
                  "baseText": {
                    "type": "string",
                    "description": "Texto base, guardado junto ao post."
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "video",
                      "image"
                    ],
                    "description": "`video` (padrão) ou `image`: foto única, só Instagram. YouTube e TikTok recusam `image` (422 `kind_not_supported_by_network`).",
                    "default": "video"
                  },
                  "media": {
                    "type": "object",
                    "properties": {
                      "video": {
                        "type": "string",
                        "description": "Id da mídia de vídeo. Obrigatório quando `kind` é `video`."
                      },
                      "cover": {
                        "type": "string",
                        "description": "Id da mídia de capa (PNG ou JPEG). Só em post de vídeo."
                      },
                      "images": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "media": {
                              "type": "string",
                              "description": "Id de uma mídia `type: image` já completada."
                            },
                            "alt": {
                              "type": "string",
                              "description": "Texto alternativo (até 1000 caracteres). Vai para o Instagram quando o item publica pela Meta (`via: meta`); pelo Buffer é ignorado e o item volta com o aviso `alt_ignored`.",
                              "maxLength": 1000
                            }
                          },
                          "required": [
                            "media"
                          ]
                        },
                        "maxItems": 1,
                        "description": "Só com `kind: image`: a foto do post, UMA por enquanto (várias seriam um carrossel, que chega numa fase futura: 422 `carousel_not_supported`)."
                      }
                    },
                    "description": "Mídias do post (ids de `POST /v1/media`, já completadas)."
                  },
                  "texts": {
                    "type": "object",
                    "properties": {
                      "youtube": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string",
                            "description": "Título do vídeo (até 100 caracteres; mais longo é cortado)."
                          },
                          "text": {
                            "type": "string",
                            "description": "Descrição."
                          },
                          "pinnedComment": {
                            "type": "string",
                            "description": "Comentário fixo, publicado depois que o vídeo fica público."
                          }
                        },
                        "required": [
                          "text"
                        ],
                        "description": "Textos do YouTube."
                      },
                      "instagram": {
                        "type": "object",
                        "properties": {
                          "text": {
                            "type": "string",
                            "description": "Legenda: até 2200 caracteres e 30 hashtags."
                          },
                          "pinnedComment": {
                            "type": "string",
                            "description": "Comentário fixo."
                          }
                        },
                        "required": [
                          "text"
                        ],
                        "description": "Textos do Instagram."
                      },
                      "tiktok": {
                        "type": "object",
                        "properties": {
                          "text": {
                            "type": "string",
                            "description": "Legenda: até 2200 caracteres."
                          }
                        },
                        "required": [
                          "text"
                        ],
                        "description": "Textos do TikTok."
                      }
                    },
                    "description": "Textos por rede. Cada rede de `items` precisa ter o seu `text`."
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "network": {
                          "type": "string",
                          "enum": [
                            "youtube",
                            "instagram",
                            "tiktok"
                          ],
                          "description": "Rede do item."
                        },
                        "channel": {
                          "type": "string",
                          "description": "Id da conta daquela rede (de `GET /v1/channels`). Sem ele, o canal padrão."
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "private",
                            "scheduled",
                            "public",
                            "draft",
                            "direct",
                            "now"
                          ],
                          "description": "Como publicar. YouTube: `private`, `scheduled` ou `public`. TikTok: `draft` ou `direct`. Instagram: `now`."
                        },
                        "scheduledFor": {
                          "type": "string",
                          "description": "Horário fixo da publicação. Data e hora ISO 8601 em UTC, por exemplo `2026-11-14T20:30:00Z`. No mínimo 30 s à frente (Instagram: 5 min pelo Buffer, 25 min pela Meta; YouTube `scheduled`: 5 min)."
                        },
                        "queue": {
                          "type": "boolean",
                          "description": "`true` usa o próximo horário livre do canal (veja `PUT /v1/channels/:network/slots`)."
                        },
                        "via": {
                          "type": "string",
                          "enum": [
                            "meta",
                            "buffer"
                          ],
                          "description": "Só Instagram: por onde publicar."
                        },
                        "replaces": {
                          "type": "object",
                          "properties": {
                            "provider": {
                              "type": "string",
                              "enum": [
                                "buffer"
                              ],
                              "description": "Sempre `buffer`."
                            },
                            "id": {
                              "type": "string",
                              "description": "Id do post no Buffer."
                            }
                          },
                          "required": [
                            "provider",
                            "id"
                          ],
                          "description": "Só Instagram com `via: meta`: apaga este post do Buffer ao publicar."
                        },
                        "media": {
                          "type": "object",
                          "properties": {
                            "video": {
                              "type": "string",
                              "description": "Id da mídia de vídeo."
                            },
                            "cover": {
                              "type": "string",
                              "description": "Id da mídia de capa."
                            }
                          },
                          "description": "Mídias só deste item (trocam as do post). Só `video` e `cover`: imagens por item chegam numa fase futura."
                        }
                      },
                      "required": [
                        "network"
                      ]
                    },
                    "description": "Um item por rede (não pode repetir a rede)."
                  },
                  "queue": {
                    "type": "boolean",
                    "description": "Vale para todos os itens que não trouxerem `queue` nem `scheduledFor`."
                  },
                  "draft": {
                    "type": "boolean",
                    "description": "`true` cria tudo como rascunho (sem horário obrigatório).",
                    "default": false
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "format": {
                        "type": "string",
                        "enum": [
                          "long"
                        ],
                        "description": "`long` marca o post como vídeo longo (sem ele, é um Short)."
                      },
                      "youtube": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "private",
                              "scheduled",
                              "public"
                            ],
                            "description": "O mesmo de `items[].mode`."
                          },
                          "categoryId": {
                            "type": "string",
                            "description": "Categoria do vídeo.",
                            "default": "28"
                          },
                          "tags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Etiquetas do vídeo."
                          },
                          "notifySubscribers": {
                            "type": "boolean",
                            "description": "Avisar os inscritos."
                          },
                          "comment": {
                            "type": "boolean",
                            "description": "`false` não publica o comentário fixo."
                          }
                        },
                        "description": "Opções do YouTube."
                      },
                      "tiktok": {
                        "type": "object",
                        "properties": {
                          "mode": {
                            "type": "string",
                            "enum": [
                              "draft",
                              "direct"
                            ],
                            "description": "O mesmo de `items[].mode`."
                          },
                          "privacy": {
                            "type": "string",
                            "enum": [
                              "PUBLIC_TO_EVERYONE",
                              "MUTUAL_FOLLOW_FRIENDS",
                              "FOLLOWER_OF_CREATOR",
                              "SELF_ONLY"
                            ],
                            "description": "Quem vê.",
                            "default": "SELF_ONLY"
                          },
                          "coverFrameMs": {
                            "type": "integer",
                            "description": "Quadro do vídeo usado como capa, em milissegundos.",
                            "minimum": 0
                          },
                          "disableComments": {
                            "type": "boolean",
                            "description": "Desligar comentários."
                          },
                          "disableDuet": {
                            "type": "boolean",
                            "description": "Desligar dueto."
                          },
                          "disableStitch": {
                            "type": "boolean",
                            "description": "Desligar stitch."
                          },
                          "commercialContent": {
                            "type": "boolean",
                            "description": "Conteúdo comercial (exige `ownBrand` e/ou `thirdPartyBrand`)."
                          },
                          "ownBrand": {
                            "type": "boolean",
                            "description": "Divulga a própria marca."
                          },
                          "thirdPartyBrand": {
                            "type": "boolean",
                            "description": "Divulga marca de terceiros (não pode ser `SELF_ONLY` no modo `direct`)."
                          }
                        },
                        "description": "Opções do TikTok."
                      },
                      "instagram": {
                        "type": "object",
                        "properties": {
                          "via": {
                            "type": "string",
                            "enum": [
                              "meta",
                              "buffer"
                            ],
                            "description": "Por onde publicar."
                          },
                          "shareToFeed": {
                            "type": "boolean",
                            "description": "Mostrar também no feed.",
                            "default": true
                          },
                          "coverFrameMs": {
                            "type": "integer",
                            "description": "Quadro do vídeo usado como capa, em milissegundos.",
                            "minimum": 0
                          }
                        },
                        "description": "Opções do Instagram."
                      }
                    },
                    "description": "Opções por rede e gerais."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Etiquetas do post (até 40 caracteres cada)."
                  },
                  "id": {
                    "type": "string",
                    "description": "Id escolhido por você (3 a 64 caracteres entre letras, números, `-` e `_`). Sem ele, o servidor gera. Repetido dá 409 `id_exists`.",
                    "pattern": "^[A-Za-z0-9_-]{3,64}$"
                  },
                  "origin": {
                    "type": "string",
                    "description": "Quem criou o post (texto livre; padrão `api`)."
                  }
                },
                "required": [
                  "media",
                  "texts",
                  "items"
                ]
              },
              "example": {
                "title": "O teste que só falhava na segunda",
                "media": {
                  "video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
                },
                "texts": {
                  "youtube": {
                    "title": "O teste que só falhava na segunda",
                    "text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
                  }
                },
                "items": [
                  {
                    "network": "youtube",
                    "mode": "scheduled",
                    "scheduledFor": "2026-11-17T20:30:00.000Z"
                  }
                ],
                "tags": [
                  "bastidores"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "scheduledFor": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "custom": {
                            "type": "boolean"
                          },
                          "mode": {
                            "type": "string"
                          },
                          "runAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "channel",
                          "state",
                          "scheduledFor",
                          "custom",
                          "mode",
                          "runAt"
                        ]
                      }
                    }
                  },
                  "required": [
                    "id",
                    "items"
                  ]
                },
                "example": {
                  "id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
                  "items": [
                    {
                      "id": "01KA3W2Y6J9D3BNF7G2K5PQS8A",
                      "network": "youtube",
                      "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "state": "scheduled",
                      "scheduledFor": "2026-11-10T20:30:00.000Z",
                      "custom": true,
                      "mode": "scheduled",
                      "runAt": "2026-11-10T19:30:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`channel_not_connected`: A rede não está conectada.\n`no_slots`: `queue` num canal sem horários.\n`id_exists`: Já existe um post com esse `id`.\n`media_not_ready`: A mídia ainda não foi concluída.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_request`: Faltou algo; a lista completa vem em `details.errors`.\n`invalid_field`: Id, canal, modo ou etiqueta inválidos.\n`time_in_past`: O horário já passou ou está perto demais.\n`missing_schedule`: Faltou `scheduledFor`, `queue` ou `draft`.\n`media_not_found`: A mídia não existe.\n`media_type_mismatch`: A mídia não é do tipo certo (`kind: image` pede `type: image`).\n`kind_not_supported_by_network`: `kind: image` num item de YouTube ou TikTok.\n`carousel_not_supported`: Mais de uma imagem em `media.images` (carrossel chega depois).\n`invalid_image`: A imagem não é JPEG (o Instagram só aceita JPEG).\n`image_too_large`: A imagem passa de 8 MB (limite do Instagram).\n`image_aspect_ratio`: A proporção da imagem fica fora de 4:5 a 1,91:1 (o Instagram recusaria; use 1080x1350).\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "posts.list",
        "tags": [
          "Posts"
        ],
        "summary": "Listar posts",
        "description": "Lista os posts, do mais recente para o mais antigo (na fila e nos rascunhos, do mais próximo para o mais distante). Filtros: `state`, `channel`, `tag`.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Filtra pelo estado.",
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "draft",
                "sent",
                "error",
                "canceled"
              ]
            }
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id do canal ou nome da rede.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Nome de uma etiqueta.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Itens por página (1 a 200).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Quantos pular.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Cada post vem sem `actions` (elas só aparecem em `GET /v1/posts/:id`). Filtros: `state`, `channel`, `tag`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "posts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string"
                          },
                          "origin": {
                            "type": "string"
                          },
                          "media": {
                            "type": "object",
                            "properties": {
                              "video": {
                                "type": "string"
                              },
                              "cover": {
                                "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                              }
                            },
                            "required": [
                              "video",
                              "cover"
                            ]
                          },
                          "texts": {
                            "type": "object",
                            "properties": {
                              "youtube": {
                                "type": "object",
                                "properties": {
                                  "title": {
                                    "type": "string"
                                  },
                                  "text": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "title",
                                  "text"
                                ]
                              }
                            },
                            "required": [
                              "youtube"
                            ]
                          },
                          "tags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "thumb": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "items": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "network": {
                                  "type": "string"
                                },
                                "channel": {
                                  "type": "string"
                                },
                                "state": {
                                  "type": "string"
                                },
                                "scheduledFor": {
                                  "type": "string",
                                  "format": "date-time"
                                },
                                "custom": {
                                  "type": "boolean"
                                },
                                "mode": {
                                  "type": "string"
                                },
                                "runAt": {
                                  "type": "string",
                                  "format": "date-time"
                                },
                                "thumb": {
                                  "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                                }
                              },
                              "required": [
                                "id",
                                "network",
                                "channel",
                                "state",
                                "scheduledFor",
                                "custom",
                                "mode",
                                "runAt",
                                "thumb"
                              ]
                            }
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "title",
                          "state",
                          "type",
                          "kind",
                          "origin",
                          "media",
                          "texts",
                          "tags",
                          "thumb",
                          "items",
                          "createdAt",
                          "updatedAt"
                        ]
                      }
                    }
                  },
                  "required": [
                    "total",
                    "posts"
                  ]
                },
                "example": {
                  "total": 1,
                  "posts": [
                    {
                      "id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
                      "title": "O teste que só falhava na segunda (versão final)",
                      "state": "queued",
                      "type": "short",
                      "kind": "video",
                      "origin": "api",
                      "media": {
                        "video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                        "cover": null
                      },
                      "texts": {
                        "youtube": {
                          "title": "O teste que só falhava na segunda",
                          "text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
                        }
                      },
                      "tags": [
                        "bastidores",
                        "testes"
                      ],
                      "thumb": null,
                      "items": [
                        {
                          "id": "01KA3W2Y6J9D3BNF7G2K5PQS8A",
                          "network": "youtube",
                          "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                          "state": "scheduled",
                          "scheduledFor": "2026-11-10T20:30:00.000Z",
                          "custom": true,
                          "mode": "scheduled",
                          "runAt": "2026-11-10T19:30:00.000Z",
                          "thumb": null
                        }
                      ],
                      "createdAt": "2026-11-07T14:02:11.482Z",
                      "updatedAt": "2026-11-07T15:40:03.120Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/posts/adopt": {
      "post": {
        "operationId": "posts.adopt",
        "tags": [
          "Posts"
        ],
        "summary": "Adotar post já publicado",
        "description": "Registra no Praeko um Reel que já está agendado no Buffer (não chama o Buffer).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "Título do post.",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "network": {
                    "type": "string",
                    "enum": [
                      "instagram"
                    ],
                    "description": "Rede do post. Hoje só `instagram`.",
                    "default": "instagram"
                  },
                  "scheduledFor": {
                    "type": "string",
                    "description": "Horário em que o Reel sai. Data e hora ISO 8601 em UTC, por exemplo `2026-11-14T20:30:00Z`. No passado, o post entra como já enviado."
                  },
                  "external": {
                    "type": "object",
                    "properties": {
                      "provider": {
                        "type": "string",
                        "enum": [
                          "buffer"
                        ],
                        "description": "Sempre `buffer`."
                      },
                      "id": {
                        "type": "string",
                        "description": "Id do post no Buffer."
                      }
                    },
                    "required": [
                      "provider",
                      "id"
                    ],
                    "description": "Onde o post está agendado fora do Praeko."
                  },
                  "texts": {
                    "type": "object",
                    "properties": {
                      "instagram": {
                        "type": "object",
                        "properties": {
                          "text": {
                            "type": "string",
                            "description": "Legenda."
                          }
                        },
                        "description": "Legenda do Reel."
                      }
                    },
                    "description": "Textos."
                  },
                  "text": {
                    "type": "string",
                    "description": "Legenda, se não usar `texts.instagram.text`."
                  },
                  "media": {
                    "type": "object",
                    "properties": {
                      "video": {
                        "type": "string",
                        "description": "Id da mídia de vídeo."
                      },
                      "cover": {
                        "type": "string",
                        "description": "Id da mídia de capa (dá a miniatura)."
                      }
                    },
                    "description": "Mídias só para exibição no painel."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Etiquetas (até 40 caracteres cada)."
                  },
                  "id": {
                    "type": "string",
                    "description": "Id do post no Praeko (3 a 64 caracteres entre letras, números, `-` e `_`).",
                    "pattern": "^[A-Za-z0-9_-]{3,64}$"
                  }
                },
                "required": [
                  "title",
                  "scheduledFor",
                  "external"
                ]
              },
              "example": {
                "title": "Reel de segunda: o teste que falhava",
                "network": "instagram",
                "scheduledFor": "2026-11-17T20:30:00.000Z",
                "external": {
                  "provider": "buffer",
                  "id": "6720a1b3c4d5e6f708192a3b"
                },
                "texts": {
                  "instagram": {
                    "text": "O teste que só falhava na segunda. #programação"
                  }
                },
                "tags": [
                  "bastidores"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso. Devolve o post adotado, no mesmo formato de `GET /v1/posts/:id`. O item sai marcado com `adopted: true`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "state": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string"
                    },
                    "origin": {
                      "type": "string"
                    },
                    "media": {
                      "type": "object",
                      "properties": {
                        "video": {
                          "type": "string"
                        },
                        "cover": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        }
                      },
                      "required": [
                        "video",
                        "cover"
                      ]
                    },
                    "texts": {
                      "type": "object",
                      "properties": {
                        "instagram": {
                          "type": "object",
                          "properties": {
                            "text": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "text"
                          ]
                        }
                      },
                      "required": [
                        "instagram"
                      ]
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "thumb": {
                      "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "scheduledFor": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "custom": {
                            "type": "boolean"
                          },
                          "mode": {
                            "type": "string"
                          },
                          "adopted": {
                            "type": "boolean"
                          },
                          "runAt": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "thumb": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "channel",
                          "state",
                          "scheduledFor",
                          "custom",
                          "mode",
                          "runAt",
                          "thumb"
                        ]
                      }
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "actions": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "id",
                    "title",
                    "state",
                    "type",
                    "kind",
                    "origin",
                    "media",
                    "texts",
                    "tags",
                    "thumb",
                    "items",
                    "createdAt",
                    "updatedAt"
                  ]
                },
                "example": {
                  "id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
                  "title": "O teste que só falhava na segunda (versão final)",
                  "state": "queued",
                  "type": "reel",
                  "kind": "video",
                  "origin": "api",
                  "media": {
                    "video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                    "cover": null
                  },
                  "texts": {
                    "instagram": {
                      "text": "Reel de segunda: o teste que falhava"
                    }
                  },
                  "tags": [
                    "bastidores",
                    "testes"
                  ],
                  "thumb": null,
                  "items": [
                    {
                      "id": "01KA3W2Y6J9D3BNF7G2K5PQS8A",
                      "network": "instagram",
                      "channel": "01KA3V1G4C7N9PXS2WQ6HM8TDZ",
                      "state": "scheduled",
                      "scheduledFor": "2026-11-10T20:30:00.000Z",
                      "custom": true,
                      "mode": "now",
                      "adopted": true,
                      "runAt": null,
                      "thumb": null
                    }
                  ],
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "updatedAt": "2026-11-07T15:40:03.120Z",
                  "actions": []
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`already_adopted`: Esse post do Buffer já está no Praeko.\n`id_exists`: Já existe um post com esse `id`.\n`channel_not_connected`: O Instagram não está conectado.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `external`, `scheduledFor` ou `id` inválidos.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "x-praeko-lido-do-codigo": true
      }
    },
    "/v1/posts/{id}": {
      "get": {
        "operationId": "posts.get",
        "tags": [
          "Posts"
        ],
        "summary": "Ver post",
        "description": "Um post com seus itens e as ações seguintes.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do post.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "state": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string"
                    },
                    "origin": {
                      "type": "string"
                    },
                    "media": {
                      "type": "object",
                      "properties": {
                        "video": {
                          "type": "string"
                        },
                        "cover": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        }
                      },
                      "required": [
                        "video",
                        "cover"
                      ]
                    },
                    "texts": {
                      "type": "object",
                      "properties": {
                        "youtube": {
                          "type": "object",
                          "properties": {
                            "title": {
                              "type": "string"
                            },
                            "text": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "title",
                            "text"
                          ]
                        }
                      },
                      "required": [
                        "youtube"
                      ]
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "thumb": {
                      "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "scheduledFor": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "custom": {
                            "type": "boolean"
                          },
                          "mode": {
                            "type": "string"
                          },
                          "runAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "thumb": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "channel",
                          "state",
                          "scheduledFor",
                          "custom",
                          "mode",
                          "runAt",
                          "thumb"
                        ]
                      }
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "actions": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "id",
                    "title",
                    "state",
                    "type",
                    "kind",
                    "origin",
                    "media",
                    "texts",
                    "tags",
                    "thumb",
                    "items",
                    "createdAt",
                    "updatedAt",
                    "actions"
                  ]
                },
                "example": {
                  "id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
                  "title": "O teste que só falhava na segunda (versão final)",
                  "state": "queued",
                  "type": "short",
                  "kind": "video",
                  "origin": "api",
                  "media": {
                    "video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                    "cover": null
                  },
                  "texts": {
                    "youtube": {
                      "title": "O teste que só falhava na segunda",
                      "text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
                    }
                  },
                  "tags": [
                    "bastidores",
                    "testes"
                  ],
                  "thumb": null,
                  "items": [
                    {
                      "id": "01KA3W2Y6J9D3BNF7G2K5PQS8A",
                      "network": "youtube",
                      "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "state": "scheduled",
                      "scheduledFor": "2026-11-10T20:30:00.000Z",
                      "custom": true,
                      "mode": "scheduled",
                      "runAt": "2026-11-10T19:30:00.000Z",
                      "thumb": null
                    }
                  ],
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "updatedAt": "2026-11-07T15:40:03.120Z",
                  "actions": []
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`post_not_found`: O post não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "posts.update",
        "tags": [
          "Posts"
        ],
        "summary": "Editar post",
        "description": "Edita título, textos, mídia, etiquetas e itens, ou move para rascunho ou fila. Só vai o que mudou; o resto fica. Um item do YouTube já enviado e ainda agendado também muda de horário e de dados (conferido ao vivo no YouTube).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do post.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Envie só o que muda. Um post adotado do Buffer só aceita texto, horário e rascunho (o resto dá 409 `post_adopted`). **YouTube já enviado:** um item do YouTube em estado `scheduled` (o Praeko já subiu o vídeo como privado, com `publishAt` no futuro) ou `private` pode mudar `scheduledFor` (vira `videos.update` com `status.publishAt`; o resto do status é preservado), título, descrição (`texts.youtube`), `videoTags`, `categoryId` e o `pinnedComment` ainda não postado. Antes de agir o servidor confere AO VIVO no YouTube (`videos.list`, 1 unidade de cota; cada escrita custa 50). Se o vídeo já foi publicado, o horário agendado já passou, o vídeo foi apagado ou não foi enviado pelo Praeko, nada é escrito e a resposta é 409 com o motivo e o link do Studio (`https://studio.youtube.com/video/<id>/edit`). O novo horário precisa ser daqui a pelo menos 5 minutos (senão 422 `time_in_past`). Pedir o mesmo horário de novo não chama o YouTube. Um PATCH com vários itens aplica cada um por conta própria e devolve `itemResults` (um por item do YouTube já enviado: `applied`, `changed`, `fields`, `notApplied`, `reason`, `message`, `publishAt`, `studio`); se NENHUM item do pedido pôde mudar, a resposta é o 409 do primeiro. Instagram e TikTok pendentes seguem as regras de sempre.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "Novo título.",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "texts": {
                    "type": "object",
                    "properties": {
                      "youtube": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string",
                            "description": "Título."
                          },
                          "text": {
                            "type": "string",
                            "description": "Descrição."
                          },
                          "pinnedComment": {
                            "type": "string",
                            "description": "Comentário fixo."
                          }
                        },
                        "description": "Textos do YouTube."
                      },
                      "instagram": {
                        "type": "object",
                        "properties": {
                          "text": {
                            "type": "string",
                            "description": "Legenda."
                          },
                          "pinnedComment": {
                            "type": "string",
                            "description": "Comentário fixo."
                          }
                        },
                        "description": "Textos do Instagram."
                      },
                      "tiktok": {
                        "type": "object",
                        "properties": {
                          "text": {
                            "type": "string",
                            "description": "Legenda."
                          }
                        },
                        "description": "Textos do TikTok."
                      }
                    },
                    "description": "Textos por rede que mudam. Os campos enviados substituem; os outros ficam. Um `text` não pode ficar vazio nem trazer marcador como `[LINK_DO_LONGO]`."
                  },
                  "media": {
                    "type": "object",
                    "properties": {
                      "cover": {
                        "type": "string",
                        "description": "Id da nova capa (ou `null` para tirar)."
                      },
                      "video": {
                        "type": "string",
                        "description": "Id do novo vídeo (só em post adotado ou ainda em rascunho; senão 409 `media_locked`)."
                      }
                    },
                    "description": "Troca de mídia."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Etiquetas (substituem as atuais)."
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Id do item."
                        },
                        "network": {
                          "type": "string",
                          "enum": [
                            "youtube",
                            "instagram",
                            "tiktok"
                          ],
                          "description": "Rede do item (alternativa ao `id`)."
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "private",
                            "scheduled",
                            "public",
                            "draft",
                            "direct",
                            "now"
                          ],
                          "description": "Novo modo (YouTube: private, scheduled, public; TikTok: draft, direct)."
                        },
                        "scheduledFor": {
                          "type": "string",
                          "description": "Novo horário fixo. Data e hora ISO 8601 em UTC, por exemplo `2026-11-14T20:30:00Z`."
                        },
                        "queue": {
                          "type": "boolean",
                          "description": "Volta para o próximo horário livre do canal."
                        },
                        "draft": {
                          "type": "boolean",
                          "description": "Vira rascunho."
                        },
                        "privacy": {
                          "type": "string",
                          "enum": [
                            "PUBLIC_TO_EVERYONE",
                            "MUTUAL_FOLLOW_FRIENDS",
                            "FOLLOWER_OF_CREATOR",
                            "SELF_ONLY"
                          ],
                          "description": "TikTok: quem vê."
                        },
                        "coverFrameMs": {
                          "type": "integer",
                          "description": "TikTok: quadro da capa em milissegundos.",
                          "minimum": 0
                        },
                        "videoTags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "YouTube, só em vídeo já enviado e ainda agendado: tags do vídeo (substituem as atuais)."
                        },
                        "categoryId": {
                          "type": "string",
                          "description": "YouTube, só em vídeo já enviado e ainda agendado: número da categoria (ex.: `28`)."
                        }
                      }
                    },
                    "description": "Itens a editar, escolhidos por `id` ou por `network`. Só dá para editar item ainda pendente."
                  },
                  "moveTo": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "queue"
                    ],
                    "description": "Move TODOS os itens editáveis para rascunho ou para a fila."
                  }
                }
              },
              "example": {
                "title": "O teste que só falhava na segunda (versão final)",
                "tags": [
                  "bastidores",
                  "testes"
                ],
                "items": [
                  {
                    "network": "youtube",
                    "scheduledFor": "2026-11-17T20:30:00.000Z"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "state": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string"
                    },
                    "origin": {
                      "type": "string"
                    },
                    "media": {
                      "type": "object",
                      "properties": {
                        "video": {
                          "type": "string"
                        },
                        "cover": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        }
                      },
                      "required": [
                        "video",
                        "cover"
                      ]
                    },
                    "texts": {
                      "type": "object",
                      "properties": {
                        "youtube": {
                          "type": "object",
                          "properties": {
                            "title": {
                              "type": "string"
                            },
                            "text": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "title",
                            "text"
                          ]
                        }
                      },
                      "required": [
                        "youtube"
                      ]
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "thumb": {
                      "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "scheduledFor": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "custom": {
                            "type": "boolean"
                          },
                          "mode": {
                            "type": "string"
                          },
                          "runAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "thumb": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "channel",
                          "state",
                          "scheduledFor",
                          "custom",
                          "mode",
                          "runAt",
                          "thumb"
                        ]
                      }
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "actions": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "id",
                    "title",
                    "state",
                    "type",
                    "kind",
                    "origin",
                    "media",
                    "texts",
                    "tags",
                    "thumb",
                    "items",
                    "createdAt",
                    "updatedAt",
                    "actions"
                  ]
                },
                "example": {
                  "id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
                  "title": "O teste que só falhava na segunda (versão final)",
                  "state": "queued",
                  "type": "short",
                  "kind": "video",
                  "origin": "api",
                  "media": {
                    "video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                    "cover": null
                  },
                  "texts": {
                    "youtube": {
                      "title": "O teste que só falhava na segunda",
                      "text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
                    }
                  },
                  "tags": [
                    "bastidores",
                    "testes"
                  ],
                  "thumb": null,
                  "items": [
                    {
                      "id": "01KA3W2Y6J9D3BNF7G2K5PQS8A",
                      "network": "youtube",
                      "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "state": "scheduled",
                      "scheduledFor": "2026-11-10T20:30:00.000Z",
                      "custom": true,
                      "mode": "scheduled",
                      "runAt": "2026-11-10T19:30:00.000Z",
                      "thumb": null
                    }
                  ],
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "updatedAt": "2026-11-07T15:40:03.120Z",
                  "actions": []
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`post_not_found`: O post não existe.\n`item_not_found`: O item não existe neste post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`not_editable`: O item já saiu ou está em andamento (YouTube já enviado: só muda enquanto o vídeo segue privado e agendado; se o horário agendado já passou, não muda mais).\n`post_adopted`: Post adotado do Buffer: só texto, horário e rascunho mudam.\n`media_locked`: O vídeo só troca em post adotado ou em rascunho; a imagem de um post de imagem não troca por PATCH.\n`video_not_private`: YouTube já enviado: o vídeo já está público no YouTube, então o horário e os dados só mudam pelo Studio (o `details.studio` traz o link).\n`video_not_found`: YouTube já enviado: o vídeo foi apagado no YouTube.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`time_in_past`: O novo horário precisa ser daqui a pelo menos 5 minutos.\n`invalid_field`: Algum campo inválido.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "502": {
            "description": "`youtube_error`: O YouTube não respondeu ou recusou a conferência/escrita; nada foi alterado (ou o `itemResults` diz o que ficou).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "posts.cancel",
        "tags": [
          "Posts"
        ],
        "summary": "Cancelar post",
        "description": "Cancela os itens pendentes de um post.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do post.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. `canceled` traz os ids dos itens cancelados; `kept`, os itens que não puderam ser cancelados (já enviados ou em andamento), no formato dos itens do post.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "canceled": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "kept": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "id",
                    "canceled",
                    "kept"
                  ]
                },
                "example": {
                  "id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
                  "canceled": [
                    "01KA3W2Y6J9D3BNF7G2K5PQS8A"
                  ],
                  "kept": []
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`post_not_found`: O post não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/posts/{id}/publish-now": {
      "post": {
        "operationId": "posts.publishNow",
        "tags": [
          "Posts"
        ],
        "summary": "Publicar agora",
        "description": "Tira os itens pendentes da fila e publica agora (`?network=` limita a uma rede).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do post.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N"
          },
          {
            "name": "network",
            "in": "query",
            "required": false,
            "description": "Publica só os itens desta rede.",
            "schema": {
              "type": "string",
              "enum": [
                "youtube",
                "instagram",
                "tiktok"
              ]
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Devolve o post com o item já fora da fila (`runAt` agora); a publicação acontece em seguida. Use `?network=` para soltar só um item.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "state": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string"
                    },
                    "origin": {
                      "type": "string"
                    },
                    "media": {
                      "type": "object",
                      "properties": {
                        "video": {
                          "type": "string"
                        },
                        "cover": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        }
                      },
                      "required": [
                        "video",
                        "cover"
                      ]
                    },
                    "texts": {
                      "type": "object",
                      "properties": {
                        "youtube": {
                          "type": "object",
                          "properties": {
                            "title": {
                              "type": "string"
                            },
                            "text": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "title",
                            "text"
                          ]
                        }
                      },
                      "required": [
                        "youtube"
                      ]
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "thumb": {
                      "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "scheduledFor": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "custom": {
                            "type": "boolean"
                          },
                          "mode": {
                            "type": "string"
                          },
                          "runAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "thumb": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "channel",
                          "state",
                          "scheduledFor",
                          "custom",
                          "mode",
                          "runAt",
                          "thumb"
                        ]
                      }
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "actions": {
                      "type": "array",
                      "items": {}
                    }
                  },
                  "required": [
                    "id",
                    "title",
                    "state",
                    "type",
                    "kind",
                    "origin",
                    "media",
                    "texts",
                    "tags",
                    "thumb",
                    "items",
                    "createdAt",
                    "updatedAt"
                  ]
                },
                "example": {
                  "id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
                  "title": "O teste que só falhava na segunda (versão final)",
                  "state": "queued",
                  "type": "short",
                  "kind": "video",
                  "origin": "api",
                  "media": {
                    "video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
                    "cover": null
                  },
                  "texts": {
                    "youtube": {
                      "title": "O teste que só falhava na segunda",
                      "text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
                    }
                  },
                  "tags": [
                    "bastidores",
                    "testes"
                  ],
                  "thumb": null,
                  "items": [
                    {
                      "id": "01KA3W2Y6J9D3BNF7G2K5PQS8A",
                      "network": "youtube",
                      "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "state": "scheduled",
                      "scheduledFor": null,
                      "custom": false,
                      "mode": "public",
                      "runAt": "2026-11-07T14:02:11.482Z",
                      "thumb": null
                    }
                  ],
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "updatedAt": "2026-11-07T15:40:03.120Z",
                  "actions": []
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/posts/{id}/youtube/schedule": {
      "post": {
        "operationId": "posts.youtubeSchedule",
        "tags": [
          "Posts"
        ],
        "summary": "Agendar no YouTube",
        "description": "Agenda a publicação de um vídeo que já subiu para o YouTube (privado).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do post.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Sem `scheduledFor` o servidor responde 422 `missing_time`. O post precisa ter um vídeo já enviado ao YouTube (senão 404 `no_video`).",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scheduledFor": {
                    "type": "string",
                    "description": "Quando o vídeo fica público. Data e hora ISO 8601 em UTC, por exemplo `2026-11-14T20:30:00Z`."
                  }
                },
                "required": [
                  "scheduledFor"
                ]
              },
              "example": {
                "scheduledFor": "2026-11-17T20:30:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "publishAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "privacy": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "publishAt",
                    "privacy",
                    "url"
                  ]
                },
                "example": {
                  "id": "dQw4w9WgXcQ",
                  "publishAt": "2026-11-10T20:30:00.000Z",
                  "privacy": "private",
                  "url": "https://youtu.be/dQw4w9WgXcQ"
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`no_video`: O post não tem vídeo no YouTube ainda.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`missing_time`: Faltou `scheduledFor`.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "x-praeko-lido-do-codigo": true
      }
    },
    "/v1/posts/{id}/items/{item}/comment": {
      "post": {
        "operationId": "posts.itemComment",
        "tags": [
          "Posts"
        ],
        "summary": "Comentário do item",
        "description": "Libera o comentário fixo guardado de um item.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do post.",
            "schema": {
              "type": "string"
            },
            "example": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N"
          },
          {
            "name": "item",
            "in": "path",
            "required": true,
            "description": "Id do item do post (campo `items[].id` da resposta de `GET /v1/posts/:id`).",
            "schema": {
              "type": "string"
            },
            "example": "01KA3W2Y6J9D3BNF7G2K5PQS8A"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Se o comentário já tinha sido publicado, a resposta é `{ alreadyDone: true, result: … }` com o resultado guardado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "comment": {
                          "type": "boolean"
                        },
                        "url": {
                          "type": "string"
                        },
                        "reminder": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "comment",
                        "url",
                        "reminder"
                      ]
                    }
                  },
                  "required": [
                    "result"
                  ]
                },
                "example": {
                  "result": {
                    "id": "UgzK3n8vYb2Qx4mH7dB4AaABAg",
                    "comment": true,
                    "url": "https://youtu.be/dQw4w9WgXcQ?lc=UgzK3n8vYb2Qx4mH7dB4AaABAg",
                    "reminder": "a API não fixa comentário: fixe pelo app"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "x-praeko-lido-do-codigo": true
      }
    },
    "/v1/calendar": {
      "get": {
        "operationId": "calendar.get",
        "tags": [
          "Calendário"
        ],
        "summary": "Calendário",
        "description": "Itens de um período e os horários livres.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Início do período (ISO 8601).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fim do período (ISO 8601).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. `freeSlots` são os horários da semana de cada canal que ainda estão livres no período.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "from": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "to": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "scheduledFor": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "custom": {
                            "type": "boolean"
                          },
                          "mode": {
                            "type": "string"
                          },
                          "runAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "postId": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "cover": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "thumb": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "channel",
                          "state",
                          "scheduledFor",
                          "custom",
                          "mode",
                          "runAt",
                          "postId",
                          "title",
                          "cover",
                          "thumb"
                        ]
                      }
                    },
                    "freeSlots": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "network": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "scheduledFor": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "network",
                          "channel",
                          "scheduledFor"
                        ]
                      }
                    }
                  },
                  "required": [
                    "from",
                    "to",
                    "items",
                    "freeSlots"
                  ]
                },
                "example": {
                  "from": "2026-11-07T14:02:11.482Z",
                  "to": "2026-11-21T14:02:11.482Z",
                  "items": [
                    {
                      "id": "01KA3W2Y6J9D3BNF7G2K5PQS8A",
                      "network": "youtube",
                      "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "state": "scheduled",
                      "scheduledFor": "2026-11-10T20:30:00.000Z",
                      "custom": true,
                      "mode": "scheduled",
                      "runAt": "2026-11-10T19:30:00.000Z",
                      "postId": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
                      "title": "O teste que só falhava na segunda",
                      "cover": null,
                      "thumb": null
                    }
                  ],
                  "freeSlots": [
                    {
                      "network": "youtube",
                      "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "scheduledFor": "2026-11-07T22:00:00.000Z"
                    },
                    {
                      "network": "youtube",
                      "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "scheduledFor": "2026-11-09T12:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/insights": {
      "get": {
        "operationId": "insights.get",
        "tags": [
          "Insights"
        ],
        "summary": "Métricas",
        "description": "Cartões, melhores posts, resultado por canal (uma linha por conta quando a rede tem várias; `channel` filtra uma), resumo e a leitura de 48 h.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Os números `null` são \"a rede não informa\" (nunca zero). Filtros: `from`, `to`, `channel`, `network`, `type`, `tag`, `sort`, `page`, `limit`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "period": {
                      "type": "object",
                      "properties": {
                        "from": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "to": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "from",
                        "to"
                      ]
                    },
                    "previousPeriod": {
                      "type": "object",
                      "properties": {
                        "from": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "to": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "from",
                        "to"
                      ]
                    },
                    "filters": {
                      "type": "object",
                      "properties": {
                        "channel": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "type": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "tag": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        }
                      },
                      "required": [
                        "channel",
                        "type",
                        "tag"
                      ]
                    },
                    "cards": {
                      "type": "object",
                      "properties": {
                        "posts": {
                          "type": "integer"
                        },
                        "followers": {
                          "type": "integer"
                        },
                        "followersChange": {
                          "type": "integer"
                        },
                        "reactions": {
                          "type": "integer"
                        },
                        "comments": {
                          "type": "integer"
                        },
                        "engagement": {
                          "type": "integer"
                        },
                        "overallEngagement": {
                          "type": "integer"
                        },
                        "views": {
                          "type": "integer"
                        },
                        "shares": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "saves": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "reach": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "watchTimeMin": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "avgWatchTimeSec": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "followersGained": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "previous": {
                          "type": "object",
                          "properties": {
                            "posts": {
                              "type": "integer"
                            },
                            "followers": {
                              "type": "integer"
                            },
                            "followersChange": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "reactions": {
                              "type": "integer"
                            },
                            "comments": {
                              "type": "integer"
                            },
                            "engagement": {
                              "type": "integer"
                            },
                            "overallEngagement": {
                              "type": "integer"
                            },
                            "views": {
                              "type": "integer"
                            },
                            "shares": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "saves": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "reach": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "watchTimeMin": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "avgWatchTimeSec": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "followersGained": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            }
                          },
                          "required": [
                            "posts",
                            "followers",
                            "followersChange",
                            "reactions",
                            "comments",
                            "engagement",
                            "overallEngagement",
                            "views",
                            "shares",
                            "saves",
                            "reach",
                            "watchTimeMin",
                            "avgWatchTimeSec",
                            "followersGained"
                          ]
                        },
                        "change": {
                          "type": "object",
                          "properties": {
                            "posts": {
                              "type": "object",
                              "properties": {
                                "absolute": {
                                  "type": "integer"
                                },
                                "pct": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "absolute",
                                "pct"
                              ]
                            },
                            "followers": {
                              "type": "object",
                              "properties": {
                                "absolute": {
                                  "type": "integer"
                                },
                                "pct": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "absolute",
                                "pct"
                              ]
                            },
                            "reactions": {
                              "type": "object",
                              "properties": {
                                "absolute": {
                                  "type": "integer"
                                },
                                "pct": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "absolute",
                                "pct"
                              ]
                            },
                            "comments": {
                              "type": "object",
                              "properties": {
                                "absolute": {
                                  "type": "integer"
                                },
                                "pct": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "absolute",
                                "pct"
                              ]
                            },
                            "engagement": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "views": {
                              "type": "object",
                              "properties": {
                                "absolute": {
                                  "type": "integer"
                                },
                                "pct": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "absolute",
                                "pct"
                              ]
                            },
                            "shares": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "saves": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "reach": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "watchTimeMin": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "avgWatchTimeSec": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            },
                            "followersGained": {
                              "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                            }
                          },
                          "required": [
                            "posts",
                            "followers",
                            "reactions",
                            "comments",
                            "engagement",
                            "views",
                            "shares",
                            "saves",
                            "reach",
                            "watchTimeMin",
                            "avgWatchTimeSec",
                            "followersGained"
                          ]
                        }
                      },
                      "required": [
                        "posts",
                        "followers",
                        "followersChange",
                        "reactions",
                        "comments",
                        "engagement",
                        "overallEngagement",
                        "views",
                        "shares",
                        "saves",
                        "reach",
                        "watchTimeMin",
                        "avgWatchTimeSec",
                        "followersGained",
                        "change"
                      ]
                    },
                    "sources": {
                      "type": "object",
                      "properties": {
                        "current": {
                          "type": "object",
                          "properties": {}
                        },
                        "previous": {
                          "type": "object",
                          "properties": {}
                        },
                        "bufferAggregate": {
                          "type": "boolean"
                        },
                        "aggregateUpdatedAt": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "scope": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "current",
                        "bufferAggregate",
                        "aggregateUpdatedAt",
                        "scope"
                      ]
                    },
                    "topPosts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "reactions": {
                            "type": "integer"
                          },
                          "comments": {
                            "type": "integer"
                          },
                          "views": {
                            "type": "integer"
                          },
                          "engagement": {
                            "type": "integer"
                          },
                          "url": {
                            "type": "string"
                          },
                          "thumb": {
                            "type": "string"
                          },
                          "thumbType": {
                            "type": "string"
                          },
                          "noMetrics": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "title",
                          "network",
                          "type",
                          "publishedAt",
                          "reactions",
                          "comments",
                          "views",
                          "engagement",
                          "url",
                          "thumb",
                          "noMetrics"
                        ]
                      }
                    },
                    "channels": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "network": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "avatar": {
                            "type": "string"
                          },
                          "posts": {
                            "type": "integer"
                          },
                          "reactions": {
                            "type": "integer"
                          },
                          "comments": {
                            "type": "integer"
                          },
                          "views": {
                            "type": "integer"
                          },
                          "engagement": {
                            "type": "integer"
                          },
                          "shares": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "saves": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "reach": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "watchTimeMin": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "avgWatchTimeSec": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          }
                        },
                        "required": [
                          "network",
                          "name",
                          "posts",
                          "reactions",
                          "comments",
                          "views",
                          "engagement",
                          "shares",
                          "saves",
                          "reach",
                          "watchTimeMin",
                          "avgWatchTimeSec"
                        ]
                      }
                    },
                    "summary": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "text": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "title",
                          "text"
                        ]
                      }
                    },
                    "posts": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "page": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "sort": {
                          "type": "string"
                        },
                        "direction": {
                          "type": "string"
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "network": {
                                "type": "string"
                              },
                              "channelId": {
                                "type": "string"
                              },
                              "title": {
                                "type": "string"
                              },
                              "type": {
                                "type": "string"
                              },
                              "publishedAt": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "url": {
                                "type": "string"
                              },
                              "origin": {
                                "type": "string"
                              },
                              "thumb": {
                                "type": "string"
                              },
                              "thumbType": {
                                "type": "string"
                              },
                              "reactions": {
                                "type": "integer"
                              },
                              "comments": {
                                "type": "integer"
                              },
                              "views": {
                                "type": "integer"
                              },
                              "shares": {
                                "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                              },
                              "saves": {
                                "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                              },
                              "reach": {
                                "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                              },
                              "watchTimeMin": {
                                "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                              },
                              "avgWatchTimeSec": {
                                "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                              },
                              "engagement": {
                                "type": "integer"
                              },
                              "noMetrics": {
                                "type": "boolean"
                              },
                              "postId": {
                                "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                              },
                              "tags": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "id",
                              "network",
                              "title",
                              "type",
                              "publishedAt",
                              "url",
                              "origin",
                              "thumb",
                              "reactions",
                              "comments",
                              "views",
                              "shares",
                              "saves",
                              "reach",
                              "watchTimeMin",
                              "avgWatchTimeSec",
                              "engagement",
                              "noMetrics",
                              "postId"
                            ]
                          }
                        }
                      },
                      "required": [
                        "total",
                        "page",
                        "limit",
                        "sort",
                        "direction",
                        "items"
                      ]
                    },
                    "reading48h": {
                      "type": "array",
                      "items": {}
                    },
                    "reading48hText": {
                      "type": "string"
                    },
                    "collection": {
                      "type": "object",
                      "properties": {
                        "last": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "postsWithMetrics": {
                          "type": "integer"
                        },
                        "postsWithoutMetrics": {
                          "type": "integer"
                        },
                        "collecting": {
                          "type": "boolean"
                        },
                        "storedReadings": {
                          "type": "integer"
                        },
                        "imported": {
                          "type": "integer"
                        },
                        "lastImport": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "last",
                        "postsWithMetrics",
                        "postsWithoutMetrics",
                        "collecting",
                        "storedReadings",
                        "imported",
                        "lastImport"
                      ]
                    }
                  },
                  "required": [
                    "period",
                    "previousPeriod",
                    "filters",
                    "cards",
                    "sources",
                    "topPosts",
                    "channels",
                    "summary",
                    "posts",
                    "reading48h",
                    "reading48hText",
                    "collection"
                  ]
                },
                "example": {
                  "period": {
                    "from": "2026-10-08T14:02:11.482Z",
                    "to": "2026-11-07T14:02:11.482Z"
                  },
                  "previousPeriod": {
                    "from": "2026-09-08T14:02:11.482Z",
                    "to": "2026-10-08T14:02:11.482Z"
                  },
                  "filters": {
                    "channel": null,
                    "type": null,
                    "tag": null
                  },
                  "cards": {
                    "posts": 6,
                    "followers": 1842,
                    "followersChange": 37,
                    "reactions": 412,
                    "comments": 58,
                    "engagement": 9,
                    "overallEngagement": 9,
                    "views": 5120,
                    "shares": null,
                    "saves": null,
                    "reach": null,
                    "watchTimeMin": null,
                    "avgWatchTimeSec": null,
                    "followersGained": null,
                    "previous": {
                      "posts": 4,
                      "followers": 1805,
                      "followersChange": null,
                      "reactions": 301,
                      "comments": 40,
                      "engagement": 8,
                      "overallEngagement": 8,
                      "views": 3900,
                      "shares": null,
                      "saves": null,
                      "reach": null,
                      "watchTimeMin": null,
                      "avgWatchTimeSec": null,
                      "followersGained": null
                    },
                    "change": {
                      "posts": {
                        "absolute": 2,
                        "pct": 50
                      },
                      "followers": {
                        "absolute": 37,
                        "pct": 2
                      },
                      "reactions": {
                        "absolute": 111,
                        "pct": 37
                      },
                      "comments": {
                        "absolute": 18,
                        "pct": 45
                      },
                      "engagement": null,
                      "views": {
                        "absolute": 1220,
                        "pct": 31
                      },
                      "shares": null,
                      "saves": null,
                      "reach": null,
                      "watchTimeMin": null,
                      "avgWatchTimeSec": null,
                      "followersGained": null
                    }
                  },
                  "sources": {
                    "current": {},
                    "previous": {},
                    "bufferAggregate": false,
                    "aggregateUpdatedAt": null,
                    "scope": "all"
                  },
                  "topPosts": [
                    {
                      "title": "O teste que só falhava na segunda",
                      "network": "youtube",
                      "type": "short",
                      "publishedAt": "2026-11-01T12:00:00.000Z",
                      "reactions": 87,
                      "comments": 12,
                      "views": 1240,
                      "engagement": 8,
                      "url": "https://youtu.be/dQw4w9WgXcQ",
                      "thumb": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
                      "thumbType": "url",
                      "noMetrics": false
                    }
                  ],
                  "channels": [
                    {
                      "network": "youtube",
                      "name": "Canal do Marcone",
                      "avatar": "/v1/channels/youtube/avatar",
                      "posts": 6,
                      "reactions": 412,
                      "comments": 58,
                      "views": 5120,
                      "engagement": 9,
                      "shares": null,
                      "saves": null,
                      "reach": null,
                      "watchTimeMin": null,
                      "avgWatchTimeSec": null
                    }
                  ],
                  "summary": [
                    {
                      "title": "Os Shorts técnicos lideram",
                      "text": "Os posts com bastidores de código tiveram o dobro das visualizações dos outros."
                    }
                  ],
                  "posts": {
                    "total": 1,
                    "page": 1,
                    "limit": 50,
                    "sort": "reactions",
                    "direction": "desc",
                    "items": [
                      {
                        "id": "youtube:dQw4w9WgXcQ",
                        "network": "youtube",
                        "channelId": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                        "title": "O teste que só falhava na segunda",
                        "type": "short",
                        "publishedAt": "2026-11-01T12:00:00Z",
                        "url": "https://youtu.be/dQw4w9WgXcQ",
                        "origin": "imported",
                        "thumb": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
                        "thumbType": "url",
                        "reactions": 87,
                        "comments": 12,
                        "views": 1240,
                        "shares": null,
                        "saves": null,
                        "reach": null,
                        "watchTimeMin": null,
                        "avgWatchTimeSec": null,
                        "engagement": 8,
                        "noMetrics": false,
                        "postId": null,
                        "tags": [
                          "testes"
                        ]
                      }
                    ]
                  },
                  "reading48h": [],
                  "reading48hText": "| Data | Rede | Post | Views | Curtidas | Comentários | Seguidores | Fonte |\n|---|---|---|---|---|---|---|---|",
                  "collection": {
                    "last": "2026-11-07T14:02:11.482Z",
                    "postsWithMetrics": 6,
                    "postsWithoutMetrics": 0,
                    "collecting": false,
                    "storedReadings": 24,
                    "imported": 12,
                    "lastImport": "2026-11-07T14:02:11.482Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/insights/retention": {
      "get": {
        "operationId": "insights.retention",
        "tags": [
          "Insights"
        ],
        "summary": "Retenção, impressões e CTR",
        "description": "Retenção (% assistida e tempo médio), impressões e CTR do YouTube e taxa de pulo do Instagram, por post. Dado que a rede não entrega vem `null`, com o motivo em `retention.message`. Exige a permissão `yt-analytics.readonly`, que a conexão do YouTube já pede (canais conectados antes dela precisam reautorizar uma vez).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Número que a rede ainda não entregou vem `null`, nunca 0. `retention.state`: `ready`, `pending`, `reauthorize`, `unavailable` ou `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "period": {
                      "type": "object",
                      "properties": {
                        "from": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "to": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "from",
                        "to"
                      ]
                    },
                    "channels": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "channelId": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "channelId",
                          "network",
                          "name",
                          "state"
                        ]
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "channelId": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "url": {
                            "type": "string"
                          },
                          "origin": {
                            "type": "string"
                          },
                          "views": {
                            "type": "integer"
                          },
                          "reach": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "impressions": {
                            "type": "integer"
                          },
                          "ctr": {
                            "type": "number"
                          },
                          "avgViewPercentage": {
                            "type": "number"
                          },
                          "avgWatchTimeSec": {
                            "type": "number"
                          },
                          "watchTimeMin": {
                            "type": "number"
                          },
                          "retention": {
                            "type": "object",
                            "properties": {
                              "state": {
                                "type": "string"
                              },
                              "source": {
                                "type": "string"
                              },
                              "collectedAt": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "points": {
                                "type": "integer"
                              }
                            },
                            "required": [
                              "state",
                              "source"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "channelId",
                          "title",
                          "type",
                          "publishedAt",
                          "url",
                          "origin",
                          "views",
                          "reach",
                          "impressions",
                          "ctr",
                          "avgViewPercentage",
                          "avgWatchTimeSec",
                          "watchTimeMin",
                          "retention"
                        ]
                      }
                    },
                    "collection": {
                      "type": "object",
                      "properties": {
                        "last": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "last"
                      ]
                    }
                  },
                  "required": [
                    "period",
                    "channels",
                    "total",
                    "items",
                    "collection"
                  ]
                },
                "example": {
                  "period": {
                    "from": "2026-10-08T14:02:11.482Z",
                    "to": "2026-11-07T14:02:11.482Z"
                  },
                  "channels": [
                    {
                      "channelId": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "network": "youtube",
                      "name": "Canal do Marcone",
                      "state": "ready",
                      "message": "Retenção por vídeo, impressões e CTR, pela API do YouTube Analytics."
                    }
                  ],
                  "total": 1,
                  "items": [
                    {
                      "id": "youtube:dQw4w9WgXcQ",
                      "network": "youtube",
                      "channelId": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "title": "O teste que só falhava na segunda",
                      "type": "short",
                      "publishedAt": "2026-11-01T12:00:00Z",
                      "url": "https://youtu.be/dQw4w9WgXcQ",
                      "origin": "imported",
                      "views": 1240,
                      "reach": null,
                      "impressions": 9800,
                      "ctr": 5.5,
                      "avgViewPercentage": 47.5,
                      "avgWatchTimeSec": 31.2,
                      "watchTimeMin": 2500.5,
                      "retention": {
                        "state": "ready",
                        "source": "youtube-analytics",
                        "collectedAt": "2026-11-07T15:40:03.120Z",
                        "points": 100
                      }
                    }
                  ],
                  "collection": {
                    "last": "2026-11-07T14:02:11.482Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/insights/{item}/retention": {
      "get": {
        "operationId": "insights.retention.item",
        "tags": [
          "Insights"
        ],
        "summary": "Retenção de um post",
        "description": "Curva de retenção de um post (`:item` é o id do post) e as métricas de alcance dele. Mesmo estado e mensagem da rota de lista.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "item",
            "in": "path",
            "required": true,
            "description": "Id do item do post (campo `items[].id` da resposta de `GET /v1/posts/:id`).",
            "schema": {
              "type": "string"
            },
            "example": "01KA3W2Y6J9D3BNF7G2K5PQS8A"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. `curve` tem até 100 pontos: `position` de 0 a 1 do vídeo, `watched` (acima de 1 quando o público reassiste) e `relative` (0,5 = mediana de vídeos de duração parecida).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "network": {
                          "type": "string"
                        },
                        "channelId": {
                          "type": "string"
                        },
                        "title": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "publishedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "url": {
                          "type": "string"
                        },
                        "origin": {
                          "type": "string"
                        },
                        "views": {
                          "type": "integer"
                        },
                        "reach": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "impressions": {
                          "type": "integer"
                        },
                        "ctr": {
                          "type": "number"
                        },
                        "avgViewPercentage": {
                          "type": "number"
                        },
                        "avgWatchTimeSec": {
                          "type": "number"
                        },
                        "watchTimeMin": {
                          "type": "number"
                        },
                        "retention": {
                          "type": "object",
                          "properties": {
                            "state": {
                              "type": "string"
                            },
                            "source": {
                              "type": "string"
                            },
                            "collectedAt": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "points": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "state",
                            "source"
                          ]
                        },
                        "curve": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "position": {
                                "type": "number"
                              },
                              "watched": {
                                "type": "integer"
                              },
                              "relative": {
                                "type": "number"
                              }
                            },
                            "required": [
                              "position",
                              "watched",
                              "relative"
                            ]
                          }
                        }
                      },
                      "required": [
                        "id",
                        "network",
                        "channelId",
                        "title",
                        "type",
                        "publishedAt",
                        "url",
                        "origin",
                        "views",
                        "reach",
                        "impressions",
                        "ctr",
                        "avgViewPercentage",
                        "avgWatchTimeSec",
                        "watchTimeMin",
                        "retention",
                        "curve"
                      ]
                    },
                    "channels": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "channelId": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "channelId",
                          "network",
                          "name",
                          "state"
                        ]
                      }
                    }
                  },
                  "required": [
                    "item",
                    "channels"
                  ]
                },
                "example": {
                  "item": {
                    "id": "youtube:dQw4w9WgXcQ",
                    "network": "youtube",
                    "channelId": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                    "title": "O teste que só falhava na segunda",
                    "type": "short",
                    "publishedAt": "2026-11-01T12:00:00Z",
                    "url": "https://youtu.be/dQw4w9WgXcQ",
                    "origin": "imported",
                    "views": 1240,
                    "reach": null,
                    "impressions": 9800,
                    "ctr": 5.5,
                    "avgViewPercentage": 47.5,
                    "avgWatchTimeSec": 31.2,
                    "watchTimeMin": 2500.5,
                    "retention": {
                      "state": "ready",
                      "source": "youtube-analytics",
                      "collectedAt": "2026-11-07T15:40:03.120Z",
                      "points": 100
                    },
                    "curve": [
                      {
                        "position": 0.01,
                        "watched": 1,
                        "relative": 0.8
                      },
                      {
                        "position": 0.5,
                        "watched": 0.42,
                        "relative": 0.55
                      },
                      {
                        "position": 1,
                        "watched": 0.1,
                        "relative": 0.3
                      }
                    ]
                  },
                  "channels": [
                    {
                      "channelId": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "network": "youtube",
                      "name": "Canal do Marcone",
                      "state": "ready",
                      "message": "Retenção por vídeo, impressões e CTR, pela API do YouTube Analytics."
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/insights/import": {
      "post": {
        "operationId": "insights.import",
        "tags": [
          "Insights"
        ],
        "summary": "Importar métricas",
        "description": "Importa o histórico do YouTube, do Buffer (Instagram) e do TikTok (todas as contas conectadas; `channel` junto com `network` limita a uma).",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "network",
            "in": "query",
            "required": false,
            "description": "Limita a uma rede (`all` importa todas).",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "youtube",
                "instagram",
                "tiktok"
              ]
            }
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Um item por rede importada. Com `dryRun`/`simulated: true` nada é gravado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "imported": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "network": {
                            "type": "string"
                          },
                          "simulated": {
                            "type": "boolean"
                          },
                          "found": {
                            "type": "integer"
                          },
                          "publicCount": {
                            "type": "integer"
                          },
                          "created": {
                            "type": "integer"
                          },
                          "updated": {
                            "type": "integer"
                          },
                          "skippedCount": {
                            "type": "integer"
                          },
                          "pages": {
                            "type": "integer"
                          },
                          "views": {
                            "type": "integer"
                          },
                          "likes": {
                            "type": "integer"
                          },
                          "comments": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "network",
                          "simulated",
                          "found",
                          "publicCount",
                          "created",
                          "updated",
                          "skippedCount",
                          "pages"
                        ]
                      }
                    }
                  },
                  "required": [
                    "imported"
                  ]
                },
                "example": {
                  "imported": [
                    {
                      "network": "youtube",
                      "simulated": false,
                      "found": 42,
                      "publicCount": 40,
                      "created": 38,
                      "updated": 2,
                      "skippedCount": 0,
                      "pages": 1,
                      "views": 51230,
                      "likes": 3120,
                      "comments": 410
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/content": {
      "get": {
        "operationId": "content.list",
        "tags": [
          "Conteúdo"
        ],
        "summary": "Conteúdo publicado",
        "description": "Tudo o que já foi publicado (pelo Praeko e importado).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Tudo que já foi publicado, do mais novo ao mais antigo: o que o Praeko publicou (`origin: \"praeko\"`) e o que foi importado (`external`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "origin": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "channelId": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "text": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "thumb": {
                            "type": "string"
                          },
                          "thumbMedia": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "type": {
                            "type": "string"
                          },
                          "views": {
                            "type": "integer"
                          },
                          "reactions": {
                            "type": "integer"
                          },
                          "comments": {
                            "type": "integer"
                          },
                          "shares": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "saves": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "reach": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "engagement": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          }
                        },
                        "required": [
                          "id",
                          "origin",
                          "network",
                          "channel",
                          "channelId",
                          "title",
                          "text",
                          "url",
                          "thumb",
                          "thumbMedia",
                          "publishedAt",
                          "type",
                          "views",
                          "reactions",
                          "comments",
                          "shares",
                          "saves",
                          "reach",
                          "engagement"
                        ]
                      }
                    }
                  },
                  "required": [
                    "total",
                    "items"
                  ]
                },
                "example": {
                  "total": 1,
                  "items": [
                    {
                      "id": "youtube:dQw4w9WgXcQ",
                      "origin": "external",
                      "network": "youtube",
                      "channel": "Canal do Marcone",
                      "channelId": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                      "title": "O teste que só falhava na segunda",
                      "text": "",
                      "url": "https://youtu.be/dQw4w9WgXcQ",
                      "thumb": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
                      "thumbMedia": null,
                      "publishedAt": "2026-11-01T12:00:00Z",
                      "type": "short",
                      "views": 1240,
                      "reactions": 87,
                      "comments": 12,
                      "shares": null,
                      "saves": null,
                      "reach": null,
                      "engagement": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/youtube/videos": {
      "get": {
        "operationId": "youtube.videos",
        "tags": [
          "YouTube"
        ],
        "summary": "Vídeos do YouTube",
        "description": "Vídeos do canal (inclusive privados e agendados); `channel` escolhe a conta.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "max",
            "in": "query",
            "required": false,
            "description": "Itens por página (até 50).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Token da próxima página (o `next` da resposta anterior).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. `next` é o token da próxima página (mande em `page`); `null` na última. `duration` é a duração em ISO 8601 (`PT58S`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "privacy": {
                            "type": "string"
                          },
                          "publishAt": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "duration": {
                            "type": "string"
                          },
                          "tags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "viewCount": {
                            "type": "integer"
                          },
                          "likes": {
                            "type": "integer"
                          },
                          "comments": {
                            "type": "integer"
                          },
                          "url": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "title",
                          "description",
                          "publishedAt",
                          "privacy",
                          "publishAt",
                          "duration",
                          "tags",
                          "viewCount",
                          "likes",
                          "comments",
                          "url"
                        ]
                      }
                    },
                    "next": {
                      "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                    }
                  },
                  "required": [
                    "videos",
                    "next"
                  ]
                },
                "example": {
                  "videos": [
                    {
                      "id": "dQw4w9WgXcQ",
                      "title": "O teste que só falhava na segunda",
                      "description": "Um teste que passava de terça a domingo.",
                      "publishedAt": "2026-11-01T12:00:00Z",
                      "privacy": "public",
                      "publishAt": null,
                      "duration": "PT58S",
                      "tags": [
                        "programação",
                        "testes"
                      ],
                      "viewCount": 1240,
                      "likes": 87,
                      "comments": 12,
                      "url": "https://youtu.be/dQw4w9WgXcQ"
                    }
                  ],
                  "next": null
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/youtube/videos/{id}": {
      "get": {
        "operationId": "youtube.video",
        "tags": [
          "YouTube"
        ],
        "summary": "Ver vídeo do YouTube",
        "description": "Um vídeo do canal.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do vídeo no YouTube.",
            "schema": {
              "type": "string"
            },
            "example": "dQw4w9WgXcQ"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "video": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "title": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "publishedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "privacy": {
                          "type": "string"
                        },
                        "publishAt": {
                          "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                        },
                        "duration": {
                          "type": "string"
                        },
                        "tags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "viewCount": {
                          "type": "integer"
                        },
                        "likes": {
                          "type": "integer"
                        },
                        "comments": {
                          "type": "integer"
                        },
                        "url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "title",
                        "description",
                        "publishedAt",
                        "privacy",
                        "publishAt",
                        "duration",
                        "tags",
                        "viewCount",
                        "likes",
                        "comments",
                        "url"
                      ]
                    }
                  },
                  "required": [
                    "video"
                  ]
                },
                "example": {
                  "video": {
                    "id": "dQw4w9WgXcQ",
                    "title": "O teste que só falhava na segunda",
                    "description": "Um teste que passava de terça a domingo.",
                    "publishedAt": "2026-11-01T12:00:00Z",
                    "privacy": "public",
                    "publishAt": null,
                    "duration": "PT58S",
                    "tags": [
                      "programação",
                      "testes"
                    ],
                    "viewCount": 1240,
                    "likes": 87,
                    "comments": 12,
                    "url": "https://youtu.be/dQw4w9WgXcQ"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "youtube.editVideo",
        "tags": [
          "YouTube"
        ],
        "summary": "Editar vídeo do YouTube",
        "description": "Edita título, descrição e tags de um vídeo (com `dryRun: true` só mostra o antes e o depois, sem gravar).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do vídeo no YouTube.",
            "schema": {
              "type": "string"
            },
            "example": "dQw4w9WgXcQ"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Pelo menos um entre `title`, `description` e `tags`. Comece com `dryRun: true`.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "Novo título (1 a 100 caracteres, sem `<` nem `>`).",
                    "maxLength": 100
                  },
                  "description": {
                    "type": "string",
                    "description": "Nova descrição (até 5000 caracteres; `\"\"` limpa).",
                    "maxLength": 5000
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Novas etiquetas (todas juntas, até 500 caracteres)."
                  },
                  "dryRun": {
                    "type": "boolean",
                    "description": "`true` só devolve o antes e o depois, sem gravar no YouTube.",
                    "default": false
                  }
                }
              },
              "example": {
                "title": "O teste que só falhava na segunda",
                "tags": [
                  "programação",
                  "testes"
                ],
                "dryRun": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "before": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "tags": {
                          "type": "array",
                          "items": {}
                        }
                      },
                      "required": [
                        "title",
                        "description",
                        "tags"
                      ]
                    },
                    "after": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "tags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "title",
                        "description",
                        "tags"
                      ]
                    },
                    "changed": {
                      "type": "boolean"
                    },
                    "written": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "id",
                    "before",
                    "after",
                    "changed",
                    "written"
                  ]
                },
                "example": {
                  "id": "dQw4w9WgXcQ",
                  "before": {
                    "title": "O teste que falhava",
                    "description": "",
                    "tags": []
                  },
                  "after": {
                    "title": "O teste que só falhava na segunda",
                    "description": "",
                    "tags": [
                      "programação",
                      "testes"
                    ]
                  },
                  "changed": true,
                  "written": false
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`youtube_not_connected`: O YouTube não está conectado.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: Título, descrição ou etiquetas fora do limite.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "502": {
            "description": "`youtube_error`: O YouTube recusou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/youtube/comments": {
      "get": {
        "operationId": "youtube.comments",
        "tags": [
          "YouTube"
        ],
        "summary": "Comentários do YouTube",
        "description": "Threads de comentários do canal (`channel` escolhe a conta).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "video",
            "in": "query",
            "required": false,
            "description": "Só as threads deste vídeo.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "unanswered",
            "in": "query",
            "required": false,
            "description": "`true` mostra só o que falta responder.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Ordem.",
            "schema": {
              "type": "string",
              "enum": [
                "recent",
                "comments",
                "views"
              ]
            }
          },
          {
            "name": "max",
            "in": "query",
            "required": false,
            "description": "Itens por página (até 100).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Token da próxima página.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "comments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "videoId": {
                            "type": "string"
                          },
                          "author": {
                            "type": "string"
                          },
                          "authorChannel": {
                            "type": "string"
                          },
                          "text": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "likes": {
                            "type": "integer"
                          },
                          "fromOwner": {
                            "type": "boolean"
                          },
                          "replyCount": {
                            "type": "integer"
                          },
                          "answered": {
                            "type": "boolean"
                          },
                          "replies": {
                            "type": "array",
                            "items": {}
                          },
                          "resolved": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "id",
                          "videoId",
                          "author",
                          "authorChannel",
                          "text",
                          "publishedAt",
                          "likes",
                          "fromOwner",
                          "replyCount",
                          "answered",
                          "replies",
                          "resolved"
                        ]
                      }
                    },
                    "next": {
                      "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                    },
                    "pending": {
                      "type": "integer"
                    },
                    "sort": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "comments",
                    "next",
                    "pending",
                    "sort"
                  ]
                },
                "example": {
                  "comments": [
                    {
                      "id": "UgzK3n8vYb2Qx4mH7dB4AaABAg",
                      "videoId": "dQw4w9WgXcQ",
                      "author": "Ana",
                      "authorChannel": "UCxk3n8vYb2Qx4mH7dB4Aana",
                      "text": "O teste que só falhava na segunda me lembrou do meu último deploy.",
                      "publishedAt": "2026-11-06T09:12:40Z",
                      "likes": 3,
                      "fromOwner": false,
                      "replyCount": 0,
                      "answered": false,
                      "replies": [],
                      "resolved": false
                    }
                  ],
                  "next": null,
                  "pending": 1,
                  "sort": "recent"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/youtube/comments/{id}/reply": {
      "post": {
        "operationId": "youtube.reply",
        "tags": [
          "YouTube"
        ],
        "summary": "Responder comentário do YouTube",
        "description": "Responde a uma thread (escreve no YouTube de verdade).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da thread de comentário do YouTube.",
            "schema": {
              "type": "string"
            },
            "example": "UgzK3n8vYb2Qx4mH7dB4AaABAg"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "description": "Texto da resposta.",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                },
                "required": [
                  "text"
                ]
              },
              "example": {
                "text": "Valeu por assistir! O código do teste está na descrição."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "parentId": {
                      "type": "string"
                    },
                    "text": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "parentId",
                    "text"
                  ]
                },
                "example": {
                  "id": "UgzK3n8vYb2Qx4mH7dB4AaABAg.9xYz",
                  "parentId": "UgzK3n8vYb2Qx4mH7dB4AaABAg",
                  "text": "Valeu por assistir! O código do teste está na descrição."
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `text` ausente ou com mais de 10000 caracteres.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "502": {
            "description": "`youtube_error`: O YouTube recusou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/youtube/comments/{id}/resolve": {
      "post": {
        "operationId": "youtube.resolve",
        "tags": [
          "YouTube"
        ],
        "summary": "Resolver comentário do YouTube",
        "description": "Marca uma thread como resolvida (só no Praeko).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da thread de comentário do YouTube.",
            "schema": {
              "type": "string"
            },
            "example": "UgzK3n8vYb2Qx4mH7dB4AaABAg"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "resolved": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "id",
                    "resolved"
                  ]
                },
                "example": {
                  "id": "UgzK3n8vYb2Qx4mH7dB4AaABAg",
                  "resolved": true
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "youtube.reopen",
        "tags": [
          "YouTube"
        ],
        "summary": "Reabrir comentário do YouTube",
        "description": "Reabre uma thread resolvida.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da thread de comentário do YouTube.",
            "schema": {
              "type": "string"
            },
            "example": "UgzK3n8vYb2Qx4mH7dB4AaABAg"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "resolved": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "id",
                    "resolved"
                  ]
                },
                "example": {
                  "id": "UgzK3n8vYb2Qx4mH7dB4AaABAg",
                  "resolved": false
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/instagram/media": {
      "get": {
        "operationId": "instagram.media",
        "tags": [
          "Instagram"
        ],
        "summary": "Mídias do Instagram",
        "description": "A grade de posts do perfil (API da Meta).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "max",
            "in": "query",
            "required": false,
            "description": "Itens por página (até 50).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Token da próxima página.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mediaItems": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "caption": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "thumbnail": {
                            "type": "string"
                          },
                          "permalink": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "likes": {
                            "type": "integer"
                          },
                          "comments": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "title",
                          "caption",
                          "type",
                          "thumbnail",
                          "permalink",
                          "publishedAt",
                          "likes",
                          "comments"
                        ]
                      }
                    },
                    "next": {
                      "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                    }
                  },
                  "required": [
                    "mediaItems",
                    "next"
                  ]
                },
                "example": {
                  "mediaItems": [
                    {
                      "id": "17858893269123456",
                      "network": "instagram",
                      "title": "Reel de segunda: o teste que falhava",
                      "caption": "Reel de segunda: o teste que falhava",
                      "type": "reel",
                      "thumbnail": "https://cdn.exemplo.com/reel.jpg",
                      "permalink": "https://www.instagram.com/reel/DAbCdEfGhIj/",
                      "publishedAt": "2026-11-04T10:57:00+0000",
                      "likes": 34,
                      "comments": 2
                    }
                  ],
                  "next": null
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/instagram/comments": {
      "get": {
        "operationId": "instagram.comments",
        "tags": [
          "Instagram"
        ],
        "summary": "Comentários do Instagram",
        "description": "Comentários do Instagram (API da Meta).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "comments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "mediaId": {
                            "type": "string"
                          },
                          "author": {
                            "type": "string"
                          },
                          "text": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "likes": {
                            "type": "integer"
                          },
                          "fromOwner": {
                            "type": "boolean"
                          },
                          "hidden": {
                            "type": "boolean"
                          },
                          "replyCount": {
                            "type": "integer"
                          },
                          "answered": {
                            "type": "boolean"
                          },
                          "replies": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "network": {
                                  "type": "string"
                                },
                                "mediaId": {
                                  "type": "string"
                                },
                                "author": {
                                  "type": "string"
                                },
                                "text": {
                                  "type": "string"
                                },
                                "publishedAt": {
                                  "type": "string",
                                  "format": "date-time"
                                },
                                "likes": {
                                  "type": "integer"
                                },
                                "fromOwner": {
                                  "type": "boolean"
                                },
                                "hidden": {
                                  "type": "boolean"
                                }
                              },
                              "required": [
                                "id",
                                "network",
                                "mediaId",
                                "author",
                                "text",
                                "publishedAt",
                                "likes",
                                "fromOwner",
                                "hidden"
                              ]
                            }
                          },
                          "media": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "title": {
                                "type": "string"
                              },
                              "thumbnail": {
                                "type": "string"
                              },
                              "type": {
                                "type": "string"
                              },
                              "permalink": {
                                "type": "string"
                              },
                              "publishedAt": {
                                "type": "string",
                                "format": "date-time"
                              }
                            },
                            "required": [
                              "id",
                              "title",
                              "thumbnail",
                              "type",
                              "permalink",
                              "publishedAt"
                            ]
                          },
                          "resolved": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "mediaId",
                          "author",
                          "text",
                          "publishedAt",
                          "likes",
                          "fromOwner",
                          "hidden",
                          "replyCount",
                          "answered",
                          "replies",
                          "media",
                          "resolved"
                        ]
                      }
                    },
                    "mediaItems": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "caption": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "thumbnail": {
                            "type": "string"
                          },
                          "permalink": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "likes": {
                            "type": "integer"
                          },
                          "comments": {
                            "type": "integer"
                          },
                          "pending": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "title",
                          "caption",
                          "type",
                          "thumbnail",
                          "permalink",
                          "publishedAt",
                          "likes",
                          "comments",
                          "pending"
                        ]
                      }
                    },
                    "next": {
                      "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                    },
                    "pending": {
                      "type": "integer"
                    },
                    "sort": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "comments",
                    "mediaItems",
                    "next",
                    "pending",
                    "sort"
                  ]
                },
                "example": {
                  "comments": [
                    {
                      "id": "17900123456789012",
                      "network": "instagram",
                      "mediaId": "17858893269123456",
                      "author": "tristao.dev",
                      "text": "Zika demais, qual banco você usa?",
                      "publishedAt": "2026-11-04T16:46:50+0000",
                      "likes": 1,
                      "fromOwner": false,
                      "hidden": false,
                      "replyCount": 1,
                      "answered": true,
                      "replies": [
                        {
                          "id": "17900123456789999",
                          "network": "instagram",
                          "mediaId": "17858893269123456",
                          "author": "dev.ocioso",
                          "text": "Valeu! É o D1 da Cloudflare.",
                          "publishedAt": "2026-11-04T19:00:00+0000",
                          "likes": 0,
                          "fromOwner": true,
                          "hidden": false
                        }
                      ],
                      "media": {
                        "id": "17858893269123456",
                        "title": "Reel de segunda: o teste que falhava",
                        "thumbnail": "https://cdn.exemplo.com/reel.jpg",
                        "type": "reel",
                        "permalink": "https://www.instagram.com/reel/DAbCdEfGhIj/",
                        "publishedAt": "2026-11-04T10:57:00+0000"
                      },
                      "resolved": false
                    }
                  ],
                  "mediaItems": [
                    {
                      "id": "17858893269123456",
                      "network": "instagram",
                      "title": "Reel de segunda: o teste que falhava",
                      "caption": "Reel de segunda: o teste que falhava",
                      "type": "reel",
                      "thumbnail": "https://cdn.exemplo.com/reel.jpg",
                      "permalink": "https://www.instagram.com/reel/DAbCdEfGhIj/",
                      "publishedAt": "2026-11-04T10:57:00+0000",
                      "likes": 34,
                      "comments": 2,
                      "pending": 0
                    }
                  ],
                  "next": null,
                  "pending": 0,
                  "sort": "recent"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/instagram/comments/{id}/reply": {
      "post": {
        "operationId": "instagram.reply",
        "tags": [
          "Instagram"
        ],
        "summary": "Responder comentário do Instagram",
        "description": "Responde a um comentário (escreve no Instagram de verdade).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do comentário do Instagram.",
            "schema": {
              "type": "string"
            },
            "example": "17858893269123456"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "description": "Texto da resposta.",
                    "minLength": 1,
                    "maxLength": 2200
                  }
                },
                "required": [
                  "text"
                ]
              },
              "example": {
                "text": "Obrigado! Foi uma segunda-feira difícil mesmo."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "parentId": {
                      "type": "string"
                    },
                    "text": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "parentId",
                    "text"
                  ]
                },
                "example": {
                  "id": "17900123456789012",
                  "parentId": "17858893269123456",
                  "text": "Obrigado! Foi uma segunda-feira difícil mesmo."
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`instagram_meta_not_connected`: A API da Meta não está conectada.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `text` ausente ou com mais de 2200 caracteres.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "502": {
            "description": "`instagram_error`: A Meta recusou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/instagram/comments/{id}/hide": {
      "post": {
        "operationId": "instagram.hide",
        "tags": [
          "Instagram"
        ],
        "summary": "Ocultar comentário do Instagram",
        "description": "Oculta ou reexibe um comentário.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do comentário do Instagram.",
            "schema": {
              "type": "string"
            },
            "example": "17858893269123456"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": false,
          "description": "O corpo é opcional: sem ele, o comentário é ocultado.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hide": {
                    "type": "boolean",
                    "description": "`true` oculta; `false` reexibe.",
                    "default": true
                  }
                }
              },
              "example": {
                "hide": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "hidden": {
                      "type": "boolean"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "id",
                    "hidden",
                    "ok"
                  ]
                },
                "example": {
                  "id": "17858893269123456",
                  "hidden": true,
                  "ok": true
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`instagram_meta_not_connected`: A API da Meta não está conectada.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "502": {
            "description": "`instagram_error`: A Meta recusou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/instagram/comments/{id}/resolve": {
      "post": {
        "operationId": "instagram.resolve",
        "tags": [
          "Instagram"
        ],
        "summary": "Resolver comentário do Instagram",
        "description": "Marca um comentário como resolvido (só no Praeko).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do comentário do Instagram.",
            "schema": {
              "type": "string"
            },
            "example": "17858893269123456"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "resolved": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "id",
                    "resolved"
                  ]
                },
                "example": {
                  "id": "17900123456789012",
                  "resolved": true
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "instagram.reopen",
        "tags": [
          "Instagram"
        ],
        "summary": "Reabrir comentário do Instagram",
        "description": "Reabre um comentário resolvido.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do comentário do Instagram.",
            "schema": {
              "type": "string"
            },
            "example": "17858893269123456"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "resolved": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "id",
                    "resolved"
                  ]
                },
                "example": {
                  "id": "17900123456789012",
                  "resolved": false
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/comments": {
      "get": {
        "operationId": "comments.list",
        "tags": [
          "Comentários"
        ],
        "summary": "Comentários de todas as redes",
        "description": "Comentários de todas as redes juntos, de TODAS as contas ativas de cada rede (cada comentário traz `channel`; `channel` filtra uma conta; uma conta que falha vai para `errors` e as outras respondem).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "network",
            "in": "query",
            "required": false,
            "description": "Limita a uma rede (`all` junta todas).",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "youtube",
                "instagram"
              ]
            }
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Id da conta quando a rede tem várias (de `GET /v1/channels`). Sem ele, o canal padrão.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Reúne os comentários de todas as contas de cada rede. `errors` diz, por rede, qual conta não respondeu (as outras vêm normalmente).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "comments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "mediaId": {
                            "type": "string"
                          },
                          "author": {
                            "type": "string"
                          },
                          "text": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "likes": {
                            "type": "integer"
                          },
                          "fromOwner": {
                            "type": "boolean"
                          },
                          "hidden": {
                            "type": "boolean"
                          },
                          "replyCount": {
                            "type": "integer"
                          },
                          "answered": {
                            "type": "boolean"
                          },
                          "replies": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "network": {
                                  "type": "string"
                                },
                                "mediaId": {
                                  "type": "string"
                                },
                                "author": {
                                  "type": "string"
                                },
                                "text": {
                                  "type": "string"
                                },
                                "publishedAt": {
                                  "type": "string",
                                  "format": "date-time"
                                },
                                "likes": {
                                  "type": "integer"
                                },
                                "fromOwner": {
                                  "type": "boolean"
                                },
                                "hidden": {
                                  "type": "boolean"
                                }
                              },
                              "required": [
                                "id",
                                "network",
                                "author",
                                "text",
                                "publishedAt",
                                "likes",
                                "fromOwner"
                              ]
                            }
                          },
                          "media": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "title": {
                                "type": "string"
                              },
                              "thumbnail": {
                                "type": "string"
                              },
                              "type": {
                                "type": "string"
                              },
                              "permalink": {
                                "type": "string"
                              },
                              "publishedAt": {
                                "type": "string",
                                "format": "date-time"
                              }
                            },
                            "required": [
                              "id",
                              "title",
                              "thumbnail",
                              "type",
                              "permalink",
                              "publishedAt"
                            ]
                          },
                          "resolved": {
                            "type": "boolean"
                          },
                          "videoId": {
                            "type": "string"
                          },
                          "authorChannel": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "author",
                          "text",
                          "publishedAt",
                          "likes",
                          "fromOwner",
                          "replyCount",
                          "answered",
                          "replies",
                          "resolved"
                        ]
                      }
                    },
                    "mediaItems": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "caption": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "thumbnail": {
                            "type": "string"
                          },
                          "permalink": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "likes": {
                            "type": "integer"
                          },
                          "comments": {
                            "type": "integer"
                          },
                          "pending": {
                            "type": "integer"
                          },
                          "channel": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "title",
                          "caption",
                          "type",
                          "thumbnail",
                          "permalink",
                          "publishedAt",
                          "likes",
                          "comments",
                          "pending"
                        ]
                      }
                    },
                    "pending": {
                      "type": "integer"
                    },
                    "pendingByNetwork": {
                      "type": "object",
                      "properties": {
                        "youtube": {
                          "type": "integer"
                        },
                        "instagram": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "youtube",
                        "instagram"
                      ]
                    },
                    "pendingByChannel": {
                      "type": "object",
                      "properties": {
                        "01KA3V0R5D8M2WQ7X4HT9NZ1BF": {
                          "type": "integer"
                        },
                        "01KA3V1G4C7N9PXS2WQ6HM8TDZ": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
                        "01KA3V1G4C7N9PXS2WQ6HM8TDZ"
                      ]
                    },
                    "errors": {
                      "type": "object",
                      "properties": {}
                    }
                  },
                  "required": [
                    "comments",
                    "mediaItems",
                    "pending",
                    "pendingByNetwork",
                    "pendingByChannel",
                    "errors"
                  ]
                },
                "example": {
                  "comments": [
                    {
                      "id": "17900123456789012",
                      "network": "instagram",
                      "mediaId": "17858893269123456",
                      "author": "tristao.dev",
                      "text": "Zika demais, qual banco você usa?",
                      "publishedAt": "2026-11-04T16:46:50+0000",
                      "likes": 1,
                      "fromOwner": false,
                      "hidden": false,
                      "replyCount": 1,
                      "answered": true,
                      "replies": [
                        {
                          "id": "17900123456789999",
                          "network": "instagram",
                          "mediaId": "17858893269123456",
                          "author": "dev.ocioso",
                          "text": "Valeu! É o D1 da Cloudflare.",
                          "publishedAt": "2026-11-04T19:00:00+0000",
                          "likes": 0,
                          "fromOwner": true,
                          "hidden": false
                        }
                      ],
                      "media": {
                        "id": "17858893269123456",
                        "title": "Reel de segunda: o teste que falhava",
                        "thumbnail": "https://cdn.exemplo.com/reel.jpg",
                        "type": "reel",
                        "permalink": "https://www.instagram.com/reel/DAbCdEfGhIj/",
                        "publishedAt": "2026-11-04T10:57:00+0000"
                      },
                      "resolved": false
                    },
                    {
                      "id": "UgzK3n8vYb2Qx4mH7dB4AaABAg",
                      "videoId": "dQw4w9WgXcQ",
                      "author": "Ana",
                      "authorChannel": "UCxk3n8vYb2Qx4mH7dB4Aana",
                      "text": "O teste que só falhava na segunda me lembrou do meu último deploy.",
                      "publishedAt": "2026-11-06T09:12:40Z",
                      "likes": 3,
                      "fromOwner": false,
                      "replyCount": 0,
                      "answered": false,
                      "replies": [],
                      "resolved": false,
                      "network": "youtube",
                      "channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF"
                    }
                  ],
                  "mediaItems": [
                    {
                      "id": "17858893269123456",
                      "network": "instagram",
                      "title": "Reel de segunda: o teste que falhava",
                      "caption": "Reel de segunda: o teste que falhava",
                      "type": "reel",
                      "thumbnail": "https://cdn.exemplo.com/reel.jpg",
                      "permalink": "https://www.instagram.com/reel/DAbCdEfGhIj/",
                      "publishedAt": "2026-11-04T10:57:00+0000",
                      "likes": 34,
                      "comments": 2,
                      "pending": 0,
                      "channel": "01KA3V1G4C7N9PXS2WQ6HM8TDZ"
                    }
                  ],
                  "pending": 1,
                  "pendingByNetwork": {
                    "youtube": 1,
                    "instagram": 0
                  },
                  "pendingByChannel": {
                    "01KA3V0R5D8M2WQ7X4HT9NZ1BF": 1,
                    "01KA3V1G4C7N9PXS2WQ6HM8TDZ": 0
                  },
                  "errors": {}
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/mentions": {
      "get": {
        "operationId": "mentions.list",
        "tags": [
          "Comentários"
        ],
        "summary": "Menções",
        "description": "Menções (Instagram por marcação; o YouTube não tem).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mentions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "network": {
                            "type": "string"
                          },
                          "author": {
                            "type": "string"
                          },
                          "text": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "publishedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "type": {
                            "type": "string"
                          },
                          "thumbnail": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "answered": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "id",
                          "network",
                          "author",
                          "text",
                          "url",
                          "publishedAt",
                          "type",
                          "thumbnail",
                          "answered"
                        ]
                      }
                    },
                    "reason": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "mentions"
                  ]
                },
                "example": {
                  "mentions": [
                    {
                      "id": "17900987654321098",
                      "network": "instagram",
                      "author": "fulano",
                      "text": "olha o @dev.ocioso",
                      "url": "https://www.instagram.com/p/DTtTtTtTtT/",
                      "publishedAt": "2026-11-01T10:00:00+0000",
                      "type": "imagem",
                      "thumbnail": null,
                      "answered": false
                    }
                  ],
                  "reason": "YouTube: a API de dados (v3) não tem endpoint de menções. Instagram: esta lista mostra as publicações em que o seu perfil foi marcado."
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api-keys": {
      "get": {
        "operationId": "apiKeys.list",
        "tags": [
          "Chaves de API"
        ],
        "summary": "Listar chaves de API",
        "description": "Lista as chaves de API (nunca o segredo).",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Nunca traz o segredo da chave: só o `prefix`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "prefix": {
                            "type": "string"
                          },
                          "scope": {
                            "type": "string"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "lastUsedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "revokedAt": {
                            "description": "Pode ser `null`; quando vem, o tipo depende do caso."
                          },
                          "expiresAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "revoked": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "prefix",
                          "scope",
                          "createdAt",
                          "lastUsedAt",
                          "revokedAt",
                          "expiresAt",
                          "revoked"
                        ]
                      }
                    }
                  },
                  "required": [
                    "keys"
                  ]
                },
                "example": {
                  "keys": [
                    {
                      "id": "01KA3Y1B9E5MK7PR3XD0V4SN2G",
                      "name": "automação do roteiro",
                      "prefix": "praeko_8vN2xQe",
                      "scope": "write",
                      "createdAt": "2026-11-07T14:02:11.482Z",
                      "lastUsedAt": "2026-11-07T15:40:03.120Z",
                      "revokedAt": null,
                      "expiresAt": "2027-02-05T14:02:11.482Z",
                      "revoked": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "apiKeys.create",
        "tags": [
          "Chaves de API"
        ],
        "summary": "Criar chave de API",
        "description": "Cria uma chave. O segredo aparece UMA vez, na resposta.",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Nome para você reconhecer a chave.",
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "read",
                      "write",
                      "admin"
                    ],
                    "description": "Escopo da chave (cada nível inclui o anterior).",
                    "default": "write"
                  },
                  "expiresInDays": {
                    "type": "integer",
                    "description": "Validade em dias. Sem ele a chave não expira.",
                    "minimum": 1,
                    "maximum": 365
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "automação do roteiro",
                "scope": "write",
                "expiresInDays": 90
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string"
                    },
                    "prefix": {
                      "type": "string"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "key": {
                      "type": "string"
                    },
                    "warning": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "scope",
                    "prefix",
                    "expiresAt",
                    "key",
                    "warning"
                  ]
                },
                "example": {
                  "id": "01KA3Y1B9E5MK7PR3XD0V4SN2G",
                  "name": "automação do roteiro",
                  "scope": "write",
                  "prefix": "praeko_8vN2xQe",
                  "expiresAt": "2027-02-05T14:02:11.482Z",
                  "key": "praeko_8vN2xQe4L1mTz7Hc0Yb5Wd3KfRj6PaUo9GtSi2XnVEwqLs",
                  "warning": "guarde esta chave agora: ela não aparece de novo"
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `name` ausente, `scope` desconhecido ou `expiresInDays` fora de 1 a 365.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api-keys/{id}": {
      "delete": {
        "operationId": "apiKeys.revoke",
        "tags": [
          "Chaves de API"
        ],
        "summary": "Revogar chave de API",
        "description": "Revoga uma chave (não dá para revogar a que está fazendo a chamada).",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da chave de API (não é a chave: é o campo `id`).",
            "schema": {
              "type": "string"
            },
            "example": "01KA3Y1B9E5MK7PR3XD0V4SN2G"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "revoked"
                  ]
                },
                "example": {
                  "revoked": "01KA3Y1B9E5MK7PR3XD0V4SN2G"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`api_key_not_found`: A chave não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`api_key_in_use`: É a chave que está fazendo esta chamada.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "usage.get",
        "tags": [
          "Uso e log"
        ],
        "summary": "Resumo de uso",
        "description": "Uso da API deste workspace nos últimos `days` (1 a 30, padrão 7, em UTC): total, com sucesso (< 400), erros, duração p95, pico de requisições em um minuto contra o limite por minuto, `byDay`, `byHour` (só com days=1: as últimas 24 horas) e `byKey` (`session` = o painel).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Quantos dias olhar (1 a 30).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Dias em UTC; `byDay` tem um item por dia do período, inclusive os sem requisição.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "days": {
                      "type": "integer"
                    },
                    "period": {
                      "type": "object",
                      "properties": {
                        "from": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "to": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "from",
                        "to"
                      ]
                    },
                    "total": {
                      "type": "integer"
                    },
                    "succeeded": {
                      "type": "integer"
                    },
                    "errors": {
                      "type": "integer"
                    },
                    "p95Ms": {
                      "type": "integer"
                    },
                    "peakPerMinute": {
                      "type": "integer"
                    },
                    "limitPerMinute": {
                      "type": "integer"
                    },
                    "byDay": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "day": {
                            "type": "string"
                          },
                          "total": {
                            "type": "integer"
                          },
                          "errors": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "day",
                          "total",
                          "errors"
                        ]
                      }
                    },
                    "byKey": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "total": {
                            "type": "integer"
                          },
                          "errors": {
                            "type": "integer"
                          },
                          "lastAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "total",
                          "errors",
                          "lastAt"
                        ]
                      }
                    }
                  },
                  "required": [
                    "days",
                    "period",
                    "total",
                    "succeeded",
                    "errors",
                    "p95Ms",
                    "peakPerMinute",
                    "limitPerMinute",
                    "byDay",
                    "byKey"
                  ]
                },
                "example": {
                  "days": 7,
                  "period": {
                    "from": "2026-11-01T00:00:00.000Z",
                    "to": "2026-11-07T14:02:11.482Z"
                  },
                  "total": 1842,
                  "succeeded": 1790,
                  "errors": 52,
                  "p95Ms": 180,
                  "peakPerMinute": 41,
                  "limitPerMinute": 300,
                  "byDay": [
                    {
                      "day": "2026-11-06",
                      "total": 312,
                      "errors": 4
                    },
                    {
                      "day": "2026-11-07",
                      "total": 98,
                      "errors": 0
                    }
                  ],
                  "byKey": [
                    {
                      "id": "01KA3Y1B9E5MK7PR3XD0V4SN2G",
                      "name": "automação do roteiro",
                      "total": 1842,
                      "errors": 52,
                      "lastAt": "2026-11-07T14:02:11.482Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage/log": {
      "get": {
        "operationId": "usage.log",
        "tags": [
          "Uso e log"
        ],
        "summary": "Log de requisições",
        "description": "Log das requisições, da mais nova para a mais antiga, guardado por 30 dias; só metadados (método, rota sem ids, status, duração, chave), nunca corpos, cabeçalhos nem query. Filtros: `key` (id ou `session`), `status` (`2xx`, `3xx`, `4xx`, `5xx` ou um código), `route` (trecho), `method`, `days` (1 a 30); `limit` (1 a 200, padrão 50) e `cursor` (o `next` da página anterior).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "Id de uma chave ou `session` (o painel).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "`2xx`, `3xx`, `4xx`, `5xx` ou um código.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "route",
            "in": "query",
            "required": false,
            "description": "Trecho da rota.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "method",
            "in": "query",
            "required": false,
            "description": "Método HTTP.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Quantos dias olhar (1 a 30).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Itens por página (1 a 200).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "O `next` da página anterior.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Só metadados (método, rota sem ids, status, duração, chave); nunca corpo, cabeçalho ou IP. Guardado por 30 dias. `next` vai em `before` para a página seguinte; `null` na última.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "method": {
                            "type": "string"
                          },
                          "route": {
                            "type": "string"
                          },
                          "status": {
                            "type": "integer"
                          },
                          "durationMs": {
                            "type": "integer"
                          },
                          "key": {
                            "type": "string"
                          },
                          "keyName": {
                            "type": "string"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "method",
                          "route",
                          "status",
                          "durationMs",
                          "key",
                          "keyName",
                          "createdAt"
                        ]
                      }
                    },
                    "next": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "records",
                    "next"
                  ]
                },
                "example": {
                  "records": [
                    {
                      "id": "01KA3Z5Q8R2WJ7CX4DN9VB6T1H",
                      "method": "POST",
                      "route": "/v1/posts",
                      "status": 201,
                      "durationMs": 212,
                      "key": "01KA3Y1B9E5MK7PR3XD0V4SN2G",
                      "keyName": "automação do roteiro",
                      "createdAt": "2026-11-07T14:02:11.482Z"
                    }
                  ],
                  "next": "01KA3Z5Q8R2WJ7CX4DN9VB6T1H"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/preferences": {
      "get": {
        "operationId": "preferences.get",
        "tags": [
          "Preferências"
        ],
        "summary": "Ver preferências",
        "description": "Preferências do painel de quem está logado neste workspace (uma chave de API lê a linha do workspace): `theme`, `language` e `notifications` por evento, com os sinais `email` e `panel`. O que falta volta com o padrão (tema escuro, pt-BR, sem e-mail, painel ligado).",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "theme": {
                      "type": "string"
                    },
                    "language": {
                      "type": "string"
                    },
                    "notifications": {
                      "type": "object",
                      "properties": {
                        "postSent": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        },
                        "postFailed": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        },
                        "scheduledFail": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        },
                        "weeklySummary": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        },
                        "newComment": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        }
                      },
                      "required": [
                        "postSent",
                        "postFailed",
                        "scheduledFail",
                        "weeklySummary",
                        "newComment"
                      ]
                    }
                  },
                  "required": [
                    "theme",
                    "language",
                    "notifications"
                  ]
                },
                "example": {
                  "theme": "dark",
                  "language": "pt-BR",
                  "notifications": {
                    "postSent": {
                      "email": false,
                      "panel": true
                    },
                    "postFailed": {
                      "email": true,
                      "panel": true
                    },
                    "scheduledFail": {
                      "email": false,
                      "panel": true
                    },
                    "weeklySummary": {
                      "email": false,
                      "panel": true
                    },
                    "newComment": {
                      "email": false,
                      "panel": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "preferences.put",
        "tags": [
          "Preferências"
        ],
        "summary": "Salvar preferências",
        "description": "Muda as preferências. Parcial (só muda o que for enviado) e estrito (campo ou valor desconhecido dá 422 `invalid_field`). Devolve as preferências completas. Os e-mails de Com `email` true o servidor manda por e-mail `postSent`, `postFailed`, `scheduledFail`, o `weeklySummary` (segunda 08:00 de São Paulo) e o `newComment` (agrupado, no máximo um e-mail por hora); com `panel` true o evento entra na central de avisos (`GET /v1/notifications`).",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Estrito: campo ou valor desconhecido dá 422 `invalid_field`. Parcial: só o que for enviado muda.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "theme": {
                    "type": "string",
                    "enum": [
                      "light",
                      "dark",
                      "system"
                    ],
                    "description": "Tema do painel."
                  },
                  "language": {
                    "type": "string",
                    "enum": [
                      "pt-BR",
                      "en"
                    ],
                    "description": "Idioma do painel."
                  },
                  "notifications": {
                    "type": "object",
                    "properties": {
                      "postSent": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "boolean",
                            "description": "Avisar por e-mail."
                          },
                          "panel": {
                            "type": "boolean",
                            "description": "Avisar no painel."
                          }
                        },
                        "description": "Onde avisar este evento."
                      },
                      "postFailed": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "boolean",
                            "description": "Avisar por e-mail."
                          },
                          "panel": {
                            "type": "boolean",
                            "description": "Avisar no painel."
                          }
                        },
                        "description": "Onde avisar este evento."
                      },
                      "scheduledFail": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "boolean",
                            "description": "Avisar por e-mail."
                          },
                          "panel": {
                            "type": "boolean",
                            "description": "Avisar no painel."
                          }
                        },
                        "description": "Onde avisar este evento."
                      },
                      "weeklySummary": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "boolean",
                            "description": "Avisar por e-mail."
                          },
                          "panel": {
                            "type": "boolean",
                            "description": "Avisar no painel."
                          }
                        },
                        "description": "Onde avisar este evento."
                      },
                      "newComment": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "boolean",
                            "description": "Avisar por e-mail."
                          },
                          "panel": {
                            "type": "boolean",
                            "description": "Avisar no painel."
                          }
                        },
                        "description": "Onde avisar este evento."
                      }
                    },
                    "description": "Avisos por evento. Só os eventos enviados mudam."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "theme": "dark",
                "language": "pt-BR",
                "notifications": {
                  "postFailed": {
                    "email": true,
                    "panel": true
                  },
                  "weeklySummary": {
                    "email": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "theme": {
                      "type": "string"
                    },
                    "language": {
                      "type": "string"
                    },
                    "notifications": {
                      "type": "object",
                      "properties": {
                        "postSent": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        },
                        "postFailed": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        },
                        "scheduledFail": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        },
                        "weeklySummary": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        },
                        "newComment": {
                          "type": "object",
                          "properties": {
                            "email": {
                              "type": "boolean"
                            },
                            "panel": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "email",
                            "panel"
                          ]
                        }
                      },
                      "required": [
                        "postSent",
                        "postFailed",
                        "scheduledFail",
                        "weeklySummary",
                        "newComment"
                      ]
                    }
                  },
                  "required": [
                    "theme",
                    "language",
                    "notifications"
                  ]
                },
                "example": {
                  "theme": "dark",
                  "language": "pt-BR",
                  "notifications": {
                    "postSent": {
                      "email": false,
                      "panel": true
                    },
                    "postFailed": {
                      "email": true,
                      "panel": true
                    },
                    "scheduledFail": {
                      "email": false,
                      "panel": true
                    },
                    "weeklySummary": {
                      "email": false,
                      "panel": true
                    },
                    "newComment": {
                      "email": false,
                      "panel": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: Campo, evento, canal ou valor desconhecido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/notifications": {
      "get": {
        "operationId": "notifications.list",
        "tags": [
          "Avisos"
        ],
        "summary": "Listar avisos do painel",
        "description": "A central de avisos (o sino) de quem está logado neste workspace, do mais novo para o mais antigo: `notifications` (`id`, `event` = postSent, postFailed, scheduledFail, weeklySummary ou newComment, `data` com os fatos do evento, `link` para onde ele leva, `read`, `readAt` e `createdAt`), `unreadCount` e `next`. O aviso só existe se a preferência `panel` daquele evento estava ligada quando ele aconteceu. Filtros: `unread` (`true`), `limit` (1 a 50, padrão 20) e `cursor` (o `next` da página anterior). Guardados por 30 dias. Uma chave de API não tem usuário e lê uma lista vazia.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "unread",
            "in": "query",
            "required": false,
            "description": "`true` devolve só os não lidos.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Itens por página (1 a 50).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "O `next` da página anterior.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "notifications": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "event": {
                            "type": "string"
                          },
                          "data": {
                            "type": "object",
                            "properties": {
                              "event": {
                                "type": "string"
                              },
                              "title": {
                                "type": "string"
                              },
                              "network": {
                                "type": "string"
                              },
                              "error": {
                                "type": "string"
                              },
                              "postId": {
                                "type": "string"
                              },
                              "count": {
                                "type": "integer"
                              },
                              "author": {
                                "type": "string"
                              },
                              "text": {
                                "type": "string"
                              },
                              "videoId": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "network"
                            ]
                          },
                          "link": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string"
                              },
                              "id": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "type"
                            ]
                          },
                          "read": {
                            "type": "boolean"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "readAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "event",
                          "data",
                          "link",
                          "read",
                          "createdAt"
                        ]
                      }
                    },
                    "unreadCount": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "notifications",
                    "unreadCount"
                  ]
                },
                "example": {
                  "notifications": [
                    {
                      "id": "01KA3ZB6T8N2QW5XH0D7MV4CRE",
                      "event": "postFailed",
                      "data": {
                        "event": "post.failed",
                        "title": "O teste que só falhava na segunda",
                        "network": "youtube",
                        "error": "o YouTube recusou o vídeo (quota diária excedida)",
                        "postId": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N"
                      },
                      "link": {
                        "type": "post",
                        "id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N"
                      },
                      "read": false,
                      "createdAt": "2026-11-07T14:02:11.482Z"
                    },
                    {
                      "id": "01KA3Z5H2C9JR7TQ0M4XB8VNDF",
                      "event": "newComment",
                      "data": {
                        "count": 3,
                        "author": "Ana",
                        "text": "Muito bom, qual editor você usa?",
                        "videoId": "dQw4w9WgXcQ",
                        "network": "youtube"
                      },
                      "link": {
                        "type": "comments"
                      },
                      "read": true,
                      "createdAt": "2026-11-07T14:02:11.482Z",
                      "readAt": "2026-11-07T14:30:00.000Z"
                    }
                  ],
                  "unreadCount": 1
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `limit` fora de 1 a 50, `unread` que não é true/false ou `cursor` inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/notifications/read": {
      "post": {
        "operationId": "notifications.read",
        "tags": [
          "Avisos"
        ],
        "summary": "Marcar avisos como lidos",
        "description": "Marca avisos como lidos: `ids` (até 100) ou `all: true`. Ids que não são seus, ou que não existem, são ignorados, e marcar de novo não faz mal. Devolve `marked` (quantos mudaram) e o novo `unreadCount`.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Envie `ids` ou `all: true`; um corpo sem nenhum dos dois dá 422 `invalid_field`.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 100,
                    "description": "Ids dos avisos a marcar (até 100). Pode ser omitido se enviar `all: true`."
                  },
                  "all": {
                    "type": "boolean",
                    "description": "`true` marca todos os avisos não lidos de quem está logado."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "all": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "marked": {
                      "type": "integer"
                    },
                    "unreadCount": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "marked",
                    "unreadCount"
                  ]
                },
                "example": {
                  "marked": 1,
                  "unreadCount": 0
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: Sem `ids` nem `all: true`, `ids` com mais de 100 itens ou campo desconhecido.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "webhooks.list",
        "tags": [
          "Webhooks"
        ],
        "summary": "Listar webhooks",
        "description": "Webhooks de saída deste workspace (nunca o segredo: só `secretHint`), com o sucesso das entregas dos últimos 7 dias (`recentDeliveries`) e os `events` que dá para assinar.",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "events": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "active": {
                            "type": "boolean"
                          },
                          "secretHint": {
                            "type": "string"
                          },
                          "consecutiveFailures": {
                            "type": "integer"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "recentDeliveries": {
                            "type": "object",
                            "properties": {
                              "total": {
                                "type": "integer"
                              },
                              "ok": {
                                "type": "integer"
                              }
                            },
                            "required": [
                              "total",
                              "ok"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "url",
                          "events",
                          "active",
                          "secretHint",
                          "consecutiveFailures",
                          "createdAt",
                          "updatedAt",
                          "recentDeliveries"
                        ]
                      }
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "webhooks",
                    "events"
                  ]
                },
                "example": {
                  "webhooks": [
                    {
                      "id": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R",
                      "url": "https://exemplo.com.br/hooks/praeko",
                      "events": [
                        "post.sent",
                        "post.failed"
                      ],
                      "active": true,
                      "secretHint": "whsec_…k3Qw",
                      "consecutiveFailures": 0,
                      "createdAt": "2026-11-07T14:02:11.482Z",
                      "updatedAt": "2026-11-07T14:02:11.482Z",
                      "recentDeliveries": {
                        "total": 12,
                        "ok": 12
                      }
                    }
                  ],
                  "events": [
                    "post.sent",
                    "post.failed",
                    "post.scheduled-fail"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "webhooks.create",
        "tags": [
          "Webhooks"
        ],
        "summary": "Criar webhook",
        "description": "Cria um webhook. A `url` precisa ser https e de um host público (IP privado, localhost e endereços de metadados de nuvem são recusados com `unsafe_url`). O `secret` de assinatura volta UMA vez. No máximo 10 por workspace.",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "Endereço que recebe os eventos. Precisa ser https, de um domínio público (sem usuário e senha; IP privado, localhost e nomes internos são recusados com `unsafe_url`).",
                    "maxLength": 2048
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "post.sent",
                        "post.failed",
                        "post.scheduled-fail"
                      ]
                    },
                    "description": "Eventos que disparam o webhook. Pelo menos um.",
                    "default": [
                      "post.sent",
                      "post.failed"
                    ]
                  },
                  "active": {
                    "type": "boolean",
                    "description": "Se o webhook começa ligado.",
                    "default": true
                  }
                },
                "required": [
                  "url"
                ]
              },
              "example": {
                "url": "https://exemplo.com.br/hooks/praeko",
                "events": [
                  "post.sent",
                  "post.failed"
                ],
                "active": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "secretHint": {
                      "type": "string"
                    },
                    "consecutiveFailures": {
                      "type": "integer"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "secret": {
                      "type": "string"
                    },
                    "warning": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "url",
                    "events",
                    "active",
                    "secretHint",
                    "consecutiveFailures",
                    "createdAt",
                    "updatedAt",
                    "secret",
                    "warning"
                  ]
                },
                "example": {
                  "id": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R",
                  "url": "https://exemplo.com.br/hooks/praeko",
                  "events": [
                    "post.sent",
                    "post.failed"
                  ],
                  "active": true,
                  "secretHint": "whsec_…k3Qw",
                  "consecutiveFailures": 0,
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "updatedAt": "2026-11-07T14:02:11.482Z",
                  "secret": "whsec_8vN2xQe4L1mTz7Hc0Yb5Wd3KfRj6PaUo9GtSi2XnVEw",
                  "warning": "guarde este segredo agora: ele não aparece de novo"
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`webhook_limit_reached`: Já são 10 webhooks neste workspace.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `url` ausente ou `events` vazio/desconhecido.\n`unsafe_url`: A url não é https, aponta para rede privada ou localhost.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "get": {
        "operationId": "webhooks.get",
        "tags": [
          "Webhooks"
        ],
        "summary": "Ver webhook",
        "description": "Um webhook (sem o segredo).",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook (começa com `wh_`).",
            "schema": {
              "type": "string"
            },
            "example": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "secretHint": {
                      "type": "string"
                    },
                    "consecutiveFailures": {
                      "type": "integer"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "url",
                    "events",
                    "active",
                    "secretHint",
                    "consecutiveFailures",
                    "createdAt",
                    "updatedAt"
                  ]
                },
                "example": {
                  "id": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R",
                  "url": "https://exemplo.com.br/hooks/praeko",
                  "events": [
                    "post.sent",
                    "post.failed"
                  ],
                  "active": true,
                  "secretHint": "whsec_…k3Qw",
                  "consecutiveFailures": 0,
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "updatedAt": "2026-11-07T14:02:11.482Z"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`webhook_not_found`: O webhook não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "webhooks.update",
        "tags": [
          "Webhooks"
        ],
        "summary": "Editar webhook",
        "description": "Muda `url`, `events` ou `active`; só vai o que mudou. Pausar descarta as entregas pendentes; reativar zera a contagem de falhas.",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook (começa com `wh_`).",
            "schema": {
              "type": "string"
            },
            "example": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Envie só os campos que quer mudar; um corpo `{}` não muda nada.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "Novo endereço (as mesmas regras de `POST /v1/webhooks`). Sem ele, a url não muda.",
                    "maxLength": 2048
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "post.sent",
                        "post.failed",
                        "post.scheduled-fail"
                      ]
                    },
                    "description": "Nova lista de eventos (substitui a anterior; pelo menos um). Sem ela, não muda."
                  },
                  "active": {
                    "type": "boolean",
                    "description": "`false` pausa (as entregas pendentes são descartadas); `true` reativa e zera a contagem de falhas seguidas."
                  }
                }
              },
              "example": {
                "events": [
                  "post.sent",
                  "post.failed",
                  "post.scheduled-fail"
                ],
                "active": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "secretHint": {
                      "type": "string"
                    },
                    "consecutiveFailures": {
                      "type": "integer"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "url",
                    "events",
                    "active",
                    "secretHint",
                    "consecutiveFailures",
                    "createdAt",
                    "updatedAt"
                  ]
                },
                "example": {
                  "id": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R",
                  "url": "https://exemplo.com.br/hooks/praeko",
                  "events": [
                    "post.sent",
                    "post.failed",
                    "post.scheduled-fail"
                  ],
                  "active": true,
                  "secretHint": "whsec_…k3Qw",
                  "consecutiveFailures": 0,
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "updatedAt": "2026-11-07T14:02:11.482Z"
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`webhook_not_found`: O webhook não existe neste workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `events` vazio ou com evento desconhecido.\n`unsafe_url`: A nova url não é aceita.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "webhooks.delete",
        "tags": [
          "Webhooks"
        ],
        "summary": "Apagar webhook",
        "description": "Apaga um webhook e o histórico das entregas dele.",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook (começa com `wh_`).",
            "schema": {
              "type": "string"
            },
            "example": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "deleted"
                  ]
                },
                "example": {
                  "deleted": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`webhook_not_found`: O webhook não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "operationId": "webhooks.test",
        "tags": [
          "Webhooks"
        ],
        "summary": "Testar webhook",
        "description": "Envia agora um evento `ping.test` (mesmo com o webhook pausado) e devolve o resultado em `delivery`. Não conta para a desativação automática.",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook (começa com `wh_`).",
            "schema": {
              "type": "string"
            },
            "example": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Mesmo que o endereço recuse, a resposta é 200 e `delivery.state` conta o resultado (`delivered`, `pending` com `nextAttemptAt`, `failed`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "delivery": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "event": {
                          "type": "string"
                        },
                        "eventId": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string"
                        },
                        "attempts": {
                          "type": "integer"
                        },
                        "lastStatus": {
                          "type": "integer"
                        },
                        "durationMs": {
                          "type": "integer"
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "deliveredAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "event",
                        "eventId",
                        "state",
                        "attempts",
                        "createdAt"
                      ]
                    }
                  },
                  "required": [
                    "delivery"
                  ]
                },
                "example": {
                  "delivery": {
                    "id": "whd_01KA3X9F2Z4WJ8G6D1RT5YQK3C",
                    "event": "post.sent",
                    "eventId": "evt_01KA3XB7W3N8QZ5RD2VH6TM9KF",
                    "state": "delivered",
                    "attempts": 1,
                    "lastStatus": 200,
                    "durationMs": 142,
                    "createdAt": "2026-11-07T14:02:11.482Z",
                    "deliveredAt": "2026-11-07T14:02:11.482Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`webhook_not_found`: O webhook não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries": {
      "get": {
        "operationId": "webhooks.deliveries",
        "tags": [
          "Webhooks"
        ],
        "summary": "Entregas do webhook",
        "description": "Entregas recentes, da mais nova para a mais antiga (`limit` de 1 a 100, padrão 20; guardadas por 30 dias): evento, estado (pending, delivered, failed, discarded), tentativas, último status HTTP e erro, duração.",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook (começa com `wh_`).",
            "schema": {
              "type": "string"
            },
            "example": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Quantas entregas (1 a 100).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deliveries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "event": {
                            "type": "string"
                          },
                          "eventId": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          },
                          "attempts": {
                            "type": "integer"
                          },
                          "lastStatus": {
                            "type": "integer"
                          },
                          "durationMs": {
                            "type": "integer"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "deliveredAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "event",
                          "eventId",
                          "state",
                          "attempts",
                          "createdAt"
                        ]
                      }
                    }
                  },
                  "required": [
                    "deliveries"
                  ]
                },
                "example": {
                  "deliveries": [
                    {
                      "id": "whd_01KA3X9F2Z4WJ8G6D1RT5YQK3C",
                      "event": "post.sent",
                      "eventId": "evt_01KA3XB7W3N8QZ5RD2VH6TM9KF",
                      "state": "delivered",
                      "attempts": 1,
                      "lastStatus": 200,
                      "durationMs": 142,
                      "createdAt": "2026-11-07T14:02:11.482Z",
                      "deliveredAt": "2026-11-07T14:02:11.482Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`webhook_not_found`: O webhook não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `limit` fora de 1 a 100.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries/{delivery}/retry": {
      "post": {
        "operationId": "webhooks.redeliver",
        "tags": [
          "Webhooks"
        ],
        "summary": "Reenviar entrega",
        "description": "Reenvia uma entrega agora, com o mesmo id `Praeko-Delivery` (quem recebe consegue deduplicar).",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook (começa com `wh_`).",
            "schema": {
              "type": "string"
            },
            "example": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R"
          },
          {
            "name": "delivery",
            "in": "path",
            "required": true,
            "description": "Id da entrega (campo `id` de `GET /v1/webhooks/:id/deliveries`).",
            "schema": {
              "type": "string"
            },
            "example": "01KA3X9F2Z4WJ8G6D1RT5YQK3C"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso. Reenvia com o mesmo `Praeko-Delivery`, para o receptor reconhecer a repetição.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "delivery": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "event": {
                          "type": "string"
                        },
                        "eventId": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string"
                        },
                        "attempts": {
                          "type": "integer"
                        },
                        "lastStatus": {
                          "type": "integer"
                        },
                        "durationMs": {
                          "type": "integer"
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "deliveredAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "event",
                        "eventId",
                        "state",
                        "attempts",
                        "createdAt"
                      ]
                    }
                  },
                  "required": [
                    "delivery"
                  ]
                },
                "example": {
                  "delivery": {
                    "id": "whd_01KA3X9F2Z4WJ8G6D1RT5YQK3C",
                    "event": "post.sent",
                    "eventId": "evt_01KA3XB7W3N8QZ5RD2VH6TM9KF",
                    "state": "delivered",
                    "attempts": 2,
                    "lastStatus": 200,
                    "durationMs": 142,
                    "createdAt": "2026-11-07T14:02:11.482Z",
                    "deliveredAt": "2026-11-07T14:02:11.482Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`webhook_not_found`: O webhook não existe.\n`delivery_not_found`: A entrega não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/rotate-secret": {
      "post": {
        "operationId": "webhooks.rotateSecret",
        "tags": [
          "Webhooks"
        ],
        "summary": "Trocar segredo do webhook",
        "description": "Troca o segredo de assinatura: o novo `secret` volta UMA vez e o antigo para de assinar na hora.",
        "x-praeko-scope": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook (começa com `wh_`).",
            "schema": {
              "type": "string"
            },
            "example": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "secret": {
                      "type": "string"
                    },
                    "secretHint": {
                      "type": "string"
                    },
                    "warning": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "secret",
                    "secretHint",
                    "warning"
                  ]
                },
                "example": {
                  "id": "wh_01KA3X4D8P6TQ1VZ0C7MH2NB5R",
                  "secret": "whsec_3Tq9Zn1VbXc5Lm8Hd2Wk7RfYj0PaUo4GtSi6XnEwKe",
                  "secretHint": "whsec_…EwKe",
                  "warning": "guarde este segredo agora: ele não aparece de novo"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`webhook_not_found`: O webhook não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ideas": {
      "get": {
        "operationId": "ideas.list",
        "tags": [
          "Ideias"
        ],
        "summary": "Listar ideias",
        "description": "O quadro de ideias deste workspace (a tela Criar), da mais nova para a mais antiga: `title`, `note`, `group` (idea, scripting, ready, posted), `tags` (a primeira é a série), `createdAt` e `updatedAt`.",
        "x-praeko-scope": "read",
        "parameters": [
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ideas": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "note": {
                            "type": "string"
                          },
                          "group": {
                            "type": "string"
                          },
                          "tags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "title",
                          "note",
                          "group",
                          "tags",
                          "createdAt",
                          "updatedAt"
                        ]
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "ideas",
                    "total"
                  ]
                },
                "example": {
                  "ideas": [
                    {
                      "id": "ide_01KA3XM5C7N0VB9H4Q2ZE6TW8F",
                      "title": "O bug que só aparecia na segunda-feira",
                      "note": "",
                      "group": "idea",
                      "tags": [
                        "bastidores"
                      ],
                      "createdAt": "2026-11-07T14:02:11.482Z",
                      "updatedAt": "2026-11-07T14:02:11.482Z"
                    }
                  ],
                  "total": 1
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "ideas.create",
        "tags": [
          "Ideias"
        ],
        "summary": "Criar ideia",
        "description": "Adiciona uma ideia. No máximo 2000 por workspace.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "Título da ideia.",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "note": {
                    "type": "string",
                    "description": "Anotações livres.",
                    "default": "",
                    "maxLength": 5000
                  },
                  "group": {
                    "type": "string",
                    "enum": [
                      "idea",
                      "scripting",
                      "ready",
                      "posted"
                    ],
                    "description": "Coluna do quadro.",
                    "default": "idea"
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 20,
                    "description": "Etiquetas (a primeira é a série). Repetidas e vazias são ignoradas."
                  }
                },
                "required": [
                  "title"
                ]
              },
              "example": {
                "title": "O bug que só aparecia na segunda-feira",
                "note": "Gravar o passo a passo do teste que falhava de segunda.",
                "group": "scripting",
                "tags": [
                  "bastidores",
                  "testes"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    },
                    "group": {
                      "type": "string"
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "title",
                    "note",
                    "group",
                    "tags",
                    "createdAt",
                    "updatedAt"
                  ]
                },
                "example": {
                  "id": "ide_01KA3XM5C7N0VB9H4Q2ZE6TW8F",
                  "title": "O bug que só aparecia na segunda-feira",
                  "note": "Gravar o passo a passo do teste que falhava de segunda.",
                  "group": "scripting",
                  "tags": [
                    "bastidores",
                    "testes"
                  ],
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "updatedAt": "2026-11-07T14:02:11.482Z"
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idea_limit_reached`: Já são 2000 ideias neste workspace.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `title` ausente ou longo demais, `group` desconhecido, `tags` fora do limite.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ideas/import": {
      "post": {
        "operationId": "ideas.import",
        "tags": [
          "Ideias"
        ],
        "summary": "Importar ideias",
        "description": "Adiciona várias ideias de uma vez (até 500 por chamada), como o painel faz com as ideias que guardava no navegador. Um item cujo `id` já foi importado é pulado (`skippedCount`), então repetir a chamada nunca duplica. Itens sem `title` válido são pulados.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ideas": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Id de origem (até 64 caracteres). Um id já importado é pulado, o que torna a chamada repetível."
                        },
                        "title": {
                          "type": "string",
                          "description": "Título (1 a 200 caracteres; mais longo é cortado). Sem título válido o item é pulado."
                        },
                        "note": {
                          "type": "string",
                          "description": "Anotações (até 5000 caracteres; mais longo é cortado)."
                        },
                        "group": {
                          "type": "string",
                          "enum": [
                            "idea",
                            "scripting",
                            "ready",
                            "posted"
                          ],
                          "description": "Coluna do quadro; valor desconhecido vira `idea`.",
                          "default": "idea"
                        },
                        "tags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Etiquetas (até 20, 40 caracteres cada; o excesso é cortado)."
                        },
                        "createdAt": {
                          "type": "string",
                          "description": "Data de criação original (ISO 8601). Só vale se for válida e não for futura; senão, agora."
                        }
                      },
                      "required": [
                        "title"
                      ]
                    },
                    "maxItems": 500,
                    "description": "Ideias a importar (até 500 por chamada)."
                  }
                },
                "required": [
                  "ideas"
                ]
              },
              "example": {
                "ideas": [
                  {
                    "id": "local-1",
                    "title": "Como o painel guarda as ideias",
                    "group": "idea",
                    "tags": [
                      "painel"
                    ],
                    "createdAt": "2026-10-28T14:00:00Z"
                  },
                  {
                    "id": "local-2",
                    "title": "Roteiro: 364 mil requisições para 42 heróis",
                    "note": "Mostrar o gráfico do log.",
                    "group": "ready",
                    "tags": [
                      "bastidores"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "imported": {
                      "type": "integer"
                    },
                    "skippedCount": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "imported",
                    "skippedCount"
                  ]
                },
                "example": {
                  "imported": 2,
                  "skippedCount": 0
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idea_limit_reached`: Passaria de 2000 ideias neste workspace.\n`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: `ideas` não é uma lista ou passa de 500 itens.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ideas/{id}": {
      "patch": {
        "operationId": "ideas.update",
        "tags": [
          "Ideias"
        ],
        "summary": "Editar ideia",
        "description": "Muda `title`, `note`, `group` (mover o cartão) ou `tags`; o que não for enviado fica.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da ideia (começa com `ide_`).",
            "schema": {
              "type": "string"
            },
            "example": "ide_01KA3XM5C7N0VB9H4Q2ZE6TW8F"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Envie só o que muda; os campos omitidos ficam como estão.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "Novo título.",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "note": {
                    "type": "string",
                    "description": "Novas anotações.",
                    "maxLength": 5000
                  },
                  "group": {
                    "type": "string",
                    "enum": [
                      "idea",
                      "scripting",
                      "ready",
                      "posted"
                    ],
                    "description": "Mover o cartão para esta coluna."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 20,
                    "description": "Nova lista de etiquetas (substitui a anterior)."
                  }
                }
              },
              "example": {
                "group": "ready",
                "tags": [
                  "bastidores",
                  "testes",
                  "serie-bugs"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    },
                    "group": {
                      "type": "string"
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "title",
                    "note",
                    "group",
                    "tags",
                    "createdAt",
                    "updatedAt"
                  ]
                },
                "example": {
                  "id": "ide_01KA3XM5C7N0VB9H4Q2ZE6TW8F",
                  "title": "O bug que só aparecia na segunda-feira",
                  "note": "Gravar o passo a passo do teste que falhava de segunda.",
                  "group": "ready",
                  "tags": [
                    "bastidores",
                    "testes",
                    "serie-bugs"
                  ],
                  "createdAt": "2026-11-07T14:02:11.482Z",
                  "updatedAt": "2026-11-07T15:40:03.120Z"
                }
              }
            }
          },
          "400": {
            "description": "`empty_body`: O corpo veio vazio.\n`invalid_json`: O corpo não é um objeto JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`idea_not_found`: A ideia não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "413": {
            "description": "`body_too_large`: O corpo passa de 1 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_field`: Algum campo fora do limite.\n`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "ideas.delete",
        "tags": [
          "Ideias"
        ],
        "summary": "Apagar ideia",
        "description": "Apaga uma ideia.",
        "x-praeko-scope": "write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da ideia (começa com `ide_`).",
            "schema": {
              "type": "string"
            },
            "example": "ide_01KA3XM5C7N0VB9H4Q2ZE6TW8F"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com `Idempotent-Replayed: true`) sem executar de novo; outro pedido com a mesma chave dá 422 `idempotency_key_reuse`; um pedido ainda em andamento dá 409 `idempotency_in_progress`. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja docs/idempotencia.md.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            },
            "example": "7f0c2a3e-5b8d-4f1a-9c64-2d1e8b7a4c90"
          },
          {
            "name": "X-Praeko-Contract",
            "in": "header",
            "required": false,
            "description": "Envie `en` para receber o contrato em inglês nos caminhos que existem nos dois contratos (por exemplo `/v1/posts`). Sempre envie.",
            "schema": {
              "type": "string",
              "enum": [
                "en"
              ],
              "default": "en"
            }
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "deleted"
                  ]
                },
                "example": {
                  "deleted": "ide_01KA3XM5C7N0VB9H4Q2ZE6TW8F"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: Faltou o cabeçalho `Authorization: Bearer …`.\n`invalid_api_key`: A chave não existe.\n`revoked_api_key`: A chave foi revogada.\n`expired_api_key`: A chave expirou.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: O escopo da chave não basta para esta rota (veja o selo de escopo).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "`idea_not_found`: A ideia não existe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_in_progress`: Um pedido com a mesma `Idempotency-Key` ainda está em andamento (repita em instantes).\n`idempotency_response_too_large`: A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "`idempotency_key_reuse`: A `Idempotency-Key` já foi usada com outro pedido (método, caminho, query ou corpo diferentes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Passou do limite de requisições por minuto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Chave de API do painel (Chaves). O escopo mínimo de cada rota está em `x-praeko-scope`."
      }
    },
    "schemas": {
      "Erro": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Código estável em snake_case."
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object"
              }
            }
          }
        }
      }
    }
  }
}
