{
  "openapi": "3.1.0",
  "info": {
    "title": "Rinck Bridge — API da nuvem",
    "version": "1",
    "summary": "Contas, planos e vínculo de máquinas do Rinck Bridge.",
    "description": "A nuvem do Rinck Bridge guarda a conta, o plano e a lista de máquinas, e aproxima os aparelhos pelo relay cifrado de ponta a ponta. Terminais, código e conversas ficam nas máquinas da pessoa e não passam por esta API.\n\n**Autenticação.** As rotas da conta pedem o token de sessão no cabeçalho `Authorization: Bearer <token>`. O token sai de `POST /v1/auth/verify` (código por e-mail) ou `POST /v1/auth/password`; com verificação em duas etapas ligada, essas rotas devolvem um tíquete de 5 minutos que se troca pelo token em `POST /v1/auth/mfa`. O token vale 30 dias, é uma sessão que a pessoa vê e encerra no app (Conta → Segurança) e cai quando ela encerra todas as sessões ou troca a senha. Não há OAuth nem chaves de API para terceiros.\n\n**Vínculo de máquina.** Um computador ou servidor entra na conta pelo fluxo de código de aparelho (no estilo da RFC 8628): `POST /v1/device/code`, a pessoa aprova em `https://rinck.com.br/ativar` e a máquina consulta `POST /v1/device/token` até receber a credencial.\n\n**Erros.** Respostas de erro são JSON `{\"error\": \"mensagem\"}` em português, com o código HTTP correspondente.\n\n**Fora desta especificação:** administração, webhooks de pagamento e e-mail, as rotas internas da máquina e do relay (licença assinada, canal cifrado, conexão entre máquinas) e a gestão da segurança da conta (Authenticator, passkeys, senha, exclusão da conta), que o app faz.",
    "contact": {
      "name": "Suporte RINCK",
      "email": "suporte@rinck.com.br",
      "url": "https://rinck.com.br/contato"
    },
    "termsOfService": "https://rinck.com.br/termos"
  },
  "externalDocs": {
    "description": "Documentação do Rinck Bridge",
    "url": "https://rinck.com.br/docs"
  },
  "servers": [
    {
      "url": "https://api.rinck.com.br",
      "description": "Nuvem do Rinck Bridge"
    }
  ],
  "tags": [
    {
      "name": "Público",
      "description": "Sem login."
    },
    {
      "name": "Entrar",
      "description": "Código por e-mail, senha e segundo fator."
    },
    {
      "name": "Máquinas",
      "description": "Vínculo por código de aparelho e máquinas da conta."
    },
    {
      "name": "Conta",
      "description": "Dados, sessões e histórico da conta."
    },
    {
      "name": "Cobrança",
      "description": "Assinatura do plano: só cartão de crédito, pela Stripe."
    }
  ],
  "paths": {
    "/v1/health": {
      "get": {
        "tags": [
          "Público"
        ],
        "operationId": "saude",
        "summary": "Estado da nuvem",
        "security": [],
        "responses": {
          "200": {
            "description": "No ar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Saude"
                }
              }
            }
          }
        }
      }
    },
    "/v1/plans": {
      "get": {
        "tags": [
          "Público"
        ],
        "operationId": "planos",
        "summary": "Planos e preços (BRL e USD, mensal e anual)",
        "security": [],
        "responses": {
          "200": {
            "description": "Planos à venda.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Planos"
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/providers": {
      "get": {
        "tags": [
          "Público"
        ],
        "operationId": "provedoresDeLogin",
        "summary": "Formas de entrar com Google e Apple ligadas",
        "security": [],
        "description": "Client IDs públicos do Google e da Apple (null quando a forma de entrar está desligada). O login com eles é feito pelo app, com o ID token do provedor.",
        "responses": {
          "200": {
            "description": "Client IDs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "google",
                    "apple"
                  ],
                  "properties": {
                    "google": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "apple": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/email": {
      "post": {
        "tags": [
          "Entrar"
        ],
        "operationId": "pedirCodigoPorEmail",
        "summary": "Mandar um código de 6 dígitos para o e-mail",
        "security": [],
        "description": "Cria a conta no primeiro acesso (depois de `/v1/auth/verify`). O código vale 10 minutos. Limites: 10 pedidos por hora por endereço de rede, 6 por hora por e-mail e um a cada 30 segundos.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Código enviado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "no_mail": {
                      "type": "boolean",
                      "description": "Esta nuvem não envia e-mail (ambiente de desenvolvimento)."
                    },
                    "dev_code": {
                      "type": "string",
                      "description": "Só no ambiente de desenvolvimento, para conta nova."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "E-mail inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Muitas tentativas. A mensagem diz quando tentar de novo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "502": {
            "description": "O e-mail não pôde ser enviado agora.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/verify": {
      "post": {
        "tags": [
          "Entrar"
        ],
        "operationId": "entrarComCodigo",
        "summary": "Trocar o código do e-mail pelo token de sessão",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "code"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "code": {
                    "type": "string",
                    "pattern": "^\\d{6}$"
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Nome, na criação da conta."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sessão criada, ou tíquete do segundo fator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Login"
                }
              }
            }
          },
          "400": {
            "description": "Código incorreto ou expirado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Conta bloqueada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Muitas tentativas. A mensagem diz quando tentar de novo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/password": {
      "post": {
        "tags": [
          "Entrar"
        ],
        "operationId": "entrarComSenha",
        "summary": "Entrar com e-mail e senha (para quem definiu uma senha)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "format": "password"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sessão criada, ou tíquete do segundo fator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Login"
                }
              }
            }
          },
          "400": {
            "description": "E-mail ou senha incorretos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Conta bloqueada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Muitas tentativas. A mensagem diz quando tentar de novo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/mfa": {
      "post": {
        "tags": [
          "Entrar"
        ],
        "operationId": "segundoFator",
        "summary": "Concluir o login com o código do Authenticator (ou de recuperação)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ticket",
                  "code"
                ],
                "properties": {
                  "ticket": {
                    "type": "string",
                    "description": "O `ticket` devolvido pelo login."
                  },
                  "code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sessão criada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "400": {
            "description": "Código incorreto ou pedido inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/logout-all": {
      "post": {
        "tags": [
          "Conta"
        ],
        "operationId": "encerrarTodasAsSessoes",
        "summary": "Encerrar todas as sessões da conta, inclusive esta",
        "security": [
          {
            "sessao": []
          }
        ],
        "responses": {
          "200": {
            "description": "Feito.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "eu",
        "summary": "Conta e o que o plano libera",
        "security": [
          {
            "sessao": []
          }
        ],
        "responses": {
          "200": {
            "description": "Conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Eu"
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Conta bloqueada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "sessoes",
        "summary": "Sessões abertas (até 50, a mais recente primeiro)",
        "security": [
          {
            "sessao": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sessões.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "sessions"
                  ],
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Sessao"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/others": {
      "delete": {
        "tags": [
          "Conta"
        ],
        "operationId": "encerrarOutrasSessoes",
        "summary": "Encerrar as outras sessões (fica só esta)",
        "security": [
          {
            "sessao": []
          }
        ],
        "responses": {
          "200": {
            "description": "Feito.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/{id}": {
      "delete": {
        "tags": [
          "Conta"
        ],
        "operationId": "encerrarSessao",
        "summary": "Encerrar uma sessão",
        "security": [
          {
            "sessao": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Feito.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/activity": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "historico",
        "summary": "Histórico da conta (últimos 60 eventos: entradas, máquinas, cobrança…)",
        "security": [
          {
            "sessao": []
          }
        ],
        "responses": {
          "200": {
            "description": "Eventos.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "events"
                  ],
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "at": {
                            "$ref": "#/components/schemas/Instante"
                          },
                          "ev": {
                            "type": "string"
                          },
                          "detail": {
                            "type": "string",
                            "description": "JSON do evento, em texto."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/export": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "exportarDados",
        "summary": "Baixar os dados que a nuvem guarda sobre a conta (LGPD)",
        "security": [
          {
            "sessao": []
          }
        ],
        "responses": {
          "200": {
            "description": "Arquivo JSON (attachment) com conta, assinatura, máquinas, passkeys, sessões e histórico.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/machines": {
      "get": {
        "tags": [
          "Máquinas"
        ],
        "operationId": "maquinas",
        "summary": "Máquinas da conta, com quem está on-line agora",
        "security": [
          {
            "sessao": []
          }
        ],
        "responses": {
          "200": {
            "description": "Máquinas.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "machines"
                  ],
                  "properties": {
                    "machines": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Maquina"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/machines/{id}": {
      "delete": {
        "tags": [
          "Máquinas"
        ],
        "operationId": "removerMaquina",
        "summary": "Tirar uma máquina da conta (ela é desconectada na hora)",
        "security": [
          {
            "sessao": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Máquina não encontrada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/device/code": {
      "post": {
        "tags": [
          "Máquinas"
        ],
        "operationId": "pedirCodigoDeAparelho",
        "summary": "Começar o vínculo de uma máquina (código de aparelho)",
        "security": [],
        "description": "A máquina gera o próprio par de chaves Ed25519 e manda a chave pública. A resposta traz o código que a pessoa confere e aprova no navegador. Limite: 20 pedidos por hora por endereço de rede.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pk"
                ],
                "properties": {
                  "pk": {
                    "type": "string",
                    "pattern": "^[\\w-]{40,50}$",
                    "description": "Chave pública Ed25519 da máquina (base64url)."
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 60,
                    "default": "Computador"
                  },
                  "platform": {
                    "type": "string",
                    "maxLength": 20,
                    "examples": [
                      "darwin",
                      "win32",
                      "linux"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Código criado (vale 10 minutos).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodigoDeAparelho"
                }
              }
            }
          },
          "400": {
            "description": "Chave pública inválida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Muitas tentativas. A mensagem diz quando tentar de novo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/device/token": {
      "post": {
        "tags": [
          "Máquinas"
        ],
        "operationId": "buscarCredencialDaMaquina",
        "summary": "Consultar o vínculo até a aprovação",
        "security": [],
        "description": "Repita a cada `interval` segundos. Enquanto a pessoa não aprova, responde 400 com `authorization_pending`. A credencial é entregue uma única vez.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "device_code"
                ],
                "properties": {
                  "device_code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Aprovado: credencial da máquina.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "machine_id",
                    "machine_token",
                    "api"
                  ],
                  "properties": {
                    "machine_id": {
                      "type": "string"
                    },
                    "machine_token": {
                      "type": "string",
                      "description": "Credencial da máquina (guarde em segredo; só as rotas internas da máquina a aceitam)."
                    },
                    "api": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ainda não aprovado, recusado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "enum": [
                        "authorization_pending",
                        "access_denied",
                        "expired_token"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/device/info": {
      "get": {
        "tags": [
          "Máquinas"
        ],
        "operationId": "verCodigoDeAparelho",
        "summary": "Ver de onde veio um pedido de vínculo antes de aprovar",
        "security": [
          {
            "sessao": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "examples": [
                "BCDF-GHJK"
              ]
            },
            "description": "O código de 8 letras mostrado pela máquina."
          }
        ],
        "responses": {
          "200": {
            "description": "Pedido pendente.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "platform": {
                      "type": "string"
                    },
                    "ip": {
                      "type": "string"
                    },
                    "place": {
                      "type": "string"
                    },
                    "at": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/Instante"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "sameNetwork": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Código inválido ou expirado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Muitas tentativas. A mensagem diz quando tentar de novo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/device/approve": {
      "post": {
        "tags": [
          "Máquinas"
        ],
        "operationId": "aprovarCodigoDeAparelho",
        "summary": "Aprovar ou recusar o vínculo de uma máquina",
        "security": [
          {
            "sessao": []
          }
        ],
        "description": "Aprovar pede plano ativo e espaço no limite de máquinas do plano.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "allow"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "allow": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Feito. Ao aprovar, devolve a máquina criada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "machine": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "402": {
            "description": "Sem plano ativo ou limite de máquinas do plano atingido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Código inválido ou expirado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Código já usado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Muitas tentativas. A mensagem diz quando tentar de novo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/checkout": {
      "post": {
        "tags": [
          "Cobrança"
        ],
        "operationId": "assinarComCartao",
        "summary": "Abrir o pagamento por cartão (Stripe Checkout)",
        "security": [
          {
            "sessao": []
          }
        ],
        "description": "Devolve o endereço da página de pagamento da Stripe, que a pessoa abre e conclui. No plano Equipe, `seats` vai de 3 a 500.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PedidoDePlano"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Página de pagamento.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Plano inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Já existe um plano ativo: a troca de plano é pelo portal (`/v1/billing/portal`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "503": {
            "description": "Preço ainda não configurado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/portal": {
      "post": {
        "tags": [
          "Cobrança"
        ],
        "operationId": "portalDaAssinatura",
        "summary": "Abrir o portal da assinatura (Stripe): trocar de plano ou de cartão, ver recibos, cancelar",
        "security": [
          {
            "sessao": []
          }
        ],
        "description": "Com plano ativo, trocar de plano é por aqui: o plano novo sai do preço escolhido no portal.",
        "responses": {
          "200": {
            "description": "Endereço do portal.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Nenhuma assinatura por cartão.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/cancel": {
      "post": {
        "tags": [
          "Cobrança"
        ],
        "operationId": "cancelarAssinatura",
        "summary": "Cancelar a assinatura",
        "security": [
          {
            "sessao": []
          }
        ],
        "description": "Nos primeiros 7 dias, devolve o valor pago e o acesso termina; depois, vale até o fim do período pago.",
        "responses": {
          "200": {
            "description": "Cancelada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "refunded",
                    "until"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "refunded": {
                      "type": "boolean",
                      "description": "Valor devolvido (cancelamento nos 7 primeiros dias)."
                    },
                    "until": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/Instante"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Até quando o plano vale."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Plano sem cobrança para cancelar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Sem token, token inválido, sessão encerrada ou expirada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Nenhuma assinatura ativa.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessao": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Token de sessão da conta (30 dias, revogável), de /v1/auth/verify, /v1/auth/password ou /v1/auth/mfa."
      }
    },
    "schemas": {
      "Erro": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Mensagem em português."
          }
        }
      },
      "Ok": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "const": true
          }
        }
      },
      "Instante": {
        "type": "integer",
        "description": "Milissegundos desde 1970 (UTC)."
      },
      "Saude": {
        "type": "object",
        "required": [
          "ok",
          "service",
          "version"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "service": {
            "const": "rinck-cloud"
          },
          "version": {
            "type": "integer"
          }
        }
      },
      "Preco": {
        "type": "object",
        "required": [
          "m",
          "y"
        ],
        "properties": {
          "m": {
            "type": "number",
            "description": "Por mês."
          },
          "y": {
            "type": "number",
            "description": "Por ano."
          }
        }
      },
      "Plano": {
        "type": "object",
        "required": [
          "name",
          "usd",
          "brl",
          "machines",
          "terminals",
          "features"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "usd": {
            "$ref": "#/components/schemas/Preco"
          },
          "brl": {
            "$ref": "#/components/schemas/Preco"
          },
          "machines": {
            "type": "integer",
            "description": "Máximo de máquinas (1000 = sem limite prático)."
          },
          "terminals": {
            "type": "integer",
            "description": "Terminais ao mesmo tempo (1000 = sem limite prático)."
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Planos": {
        "type": "object",
        "required": [
          "plans",
          "trialDays"
        ],
        "properties": {
          "plans": {
            "type": "object",
            "required": [
              "solo",
              "pro",
              "team"
            ],
            "properties": {
              "solo": {
                "$ref": "#/components/schemas/Plano"
              },
              "pro": {
                "$ref": "#/components/schemas/Plano"
              },
              "team": {
                "$ref": "#/components/schemas/Plano"
              }
            }
          },
          "trialDays": {
            "type": "integer"
          }
        },
        "description": "O plano Equipe é cobrado por pessoa, a partir de 3."
      },
      "Token": {
        "type": "object",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "Token de sessão (use em Authorization: Bearer)."
          }
        }
      },
      "Login": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/Token"
          },
          {
            "type": "object",
            "required": [
              "mfa",
              "ticket"
            ],
            "properties": {
              "mfa": {
                "const": true
              },
              "ticket": {
                "type": "string",
                "description": "Tíquete de 5 minutos para /v1/auth/mfa."
              }
            }
          }
        ]
      },
      "Direitos": {
        "type": "object",
        "required": [
          "plan",
          "active",
          "machines",
          "terminals",
          "relay",
          "features"
        ],
        "properties": {
          "plan": {
            "enum": [
              "trial",
              "solo",
              "pro",
              "team",
              "none"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "machines": {
            "type": "integer"
          },
          "terminals": {
            "type": "integer"
          },
          "relay": {
            "type": "boolean",
            "description": "Acesso remoto pela nuvem."
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "until": {
            "$ref": "#/components/schemas/Instante"
          }
        }
      },
      "Eu": {
        "type": "object",
        "required": [
          "user",
          "entitlements"
        ],
        "properties": {
          "user": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "createdAt": {
                "$ref": "#/components/schemas/Instante"
              },
              "role": {
                "type": "string"
              },
              "hasPassword": {
                "type": "boolean"
              },
              "deleteAt": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Instante"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Exclusão pedida: data em que a conta será apagada."
              }
            }
          },
          "entitlements": {
            "$ref": "#/components/schemas/Direitos"
          }
        }
      },
      "Sessao": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "created_at": {
            "$ref": "#/components/schemas/Instante"
          },
          "last_seen": {
            "$ref": "#/components/schemas/Instante"
          },
          "device": {
            "type": "string"
          },
          "ip": {
            "type": "string"
          },
          "place": {
            "type": "string"
          },
          "via": {
            "type": "string"
          },
          "current": {
            "type": "boolean"
          }
        }
      },
      "Maquina": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "pk": {
            "type": "string",
            "description": "Chave pública Ed25519 (a identidade fixada pelos aparelhos)."
          },
          "created_at": {
            "$ref": "#/components/schemas/Instante"
          },
          "last_seen": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Instante"
              },
              {
                "type": "null"
              }
            ]
          },
          "version": {
            "type": [
              "string",
              "null"
            ]
          },
          "direct": {
            "type": [
              "string",
              "null"
            ],
            "description": "Endereço direto na rede privada (Tailscale ou rede local), quando ligado."
          },
          "online": {
            "type": "boolean"
          }
        }
      },
      "CodigoDeAparelho": {
        "type": "object",
        "required": [
          "device_code",
          "user_code",
          "verification_uri",
          "verification_uri_complete",
          "interval",
          "expires_in"
        ],
        "properties": {
          "device_code": {
            "type": "string",
            "description": "Segredo da máquina para /v1/device/token."
          },
          "user_code": {
            "type": "string",
            "examples": [
              "BCDF-GHJK"
            ],
            "description": "Código que a pessoa confere."
          },
          "verification_uri": {
            "type": "string",
            "format": "uri"
          },
          "verification_uri_complete": {
            "type": "string",
            "format": "uri"
          },
          "interval": {
            "type": "integer",
            "description": "Segundos entre consultas."
          },
          "expires_in": {
            "type": "integer",
            "description": "Segundos."
          }
        }
      },
      "PedidoDePlano": {
        "type": "object",
        "required": [
          "plan"
        ],
        "properties": {
          "plan": {
            "enum": [
              "solo",
              "pro",
              "team"
            ]
          },
          "interval": {
            "enum": [
              "month",
              "year"
            ],
            "default": "month"
          },
          "seats": {
            "type": "integer",
            "minimum": 3,
            "maximum": 500,
            "description": "Só no plano Equipe."
          }
        }
      }
    }
  }
}