# Gerado de api/openapi.yaml por `make api` (internal/publicapi). Não edite à mão. components: headers: IdempotentReplayed: description: '`true` quando a resposta foi repetida de uma requisição anterior com a mesma `Idempotency-Key`. Convenção desta API, como a de outras; o rascunho da IETF não define cabeçalho de repetição.' required: false schema: enum: - "true" type: string PollAfter: description: 'Segundos sugeridos até consultar de novo (`GET /cloud/operations/{id}` ou o recurso): a operação segue em segundo plano e avança a cada 10 s.' required: false schema: type: integer RateLimit: description: 'O que resta do limite: `"";r=;t=`. A espera até a próxima requisição vem no `Retry-After`.' required: false schema: type: string RateLimitPolicy: description: 'O limite que recusou: `"";q=;w=`.' required: false schema: type: string RetryAfter: description: Segundos até poder tentar novamente. required: false schema: type: integer parameters: Competence: description: Mês de competência (`YYYY-MM`). in: query name: competence required: false schema: $ref: '#/components/schemas/Month' ContractIdQuery: in: query name: contract_id required: false schema: format: uuid type: string DueFrom: description: Vencimento a partir de (`YYYY-MM-DD`, inclusive). in: query name: due_from required: false schema: format: date type: string DueTo: description: Vencimento até (`YYYY-MM-DD`, inclusive). in: query name: due_to required: false schema: format: date type: string EndCustomerRefQuery: description: Id do cliente final no sistema do revendedor, o `end_customer_ref` do servidor. in: query name: end_customer_ref required: false schema: pattern: ^[A-Za-z0-9._:-]{1,128}$ type: string ExternalIdQuery: description: Id do pedido no sistema do revendedor, o `external_id` do servidor. in: query name: external_id required: false schema: pattern: ^[A-Za-z0-9._:-]{1,128}$ type: string Id: in: path name: id required: true schema: format: uuid type: string IdempotencyKey: description: | Chave de idempotência (draft-ietf-httpapi-idempotency-key-header-07): de 1 a 255 caracteres ASCII visíveis, como string de Structured Field (`"…"`) ou texto puro; gere uma nova (um UUID) para cada operação. Vale por 24 horas, por conta e credencial (o token, ou o usuário no painel). A mesma chave com a mesma requisição (método, caminho, query e corpo) repete a resposta guardada (status e corpo) com `Idempotent-Replayed: true`, sem cobrar nem criar de novo; com outra requisição responde 422 `idempotency.key_reused`; enquanto a primeira roda, 409 `idempotency.in_progress` com `Retry-After`. Toda resposta fica guardada, inclusive 5xx (uma falha pode vir depois de uma cobrança); só 401, 403, 409 e 429, que dizem que nada rodou, liberam a chave. Uma chave presa por uma requisição que morreu é liberada depois de 10 minutos. in: header name: Idempotency-Key required: false schema: maxLength: 257 minLength: 1 type: string Limit: description: Tamanho da página (padrão 50, máximo 200). in: query name: limit required: false schema: default: 50 maximum: 200 minimum: 1 type: integer Offset: description: Deslocamento da página. in: query name: offset required: false schema: default: 0 minimum: 0 type: integer PageCursor: description: O `next_cursor` da página anterior desta mesma lista (opaco; 400 `invalid_cursor` se não for). in: query name: cursor required: false schema: maxLength: 200 type: string PageLimit: description: | Tamanho da página, de 1 a 100. Com `limit` ou `cursor` a resposta é uma página em ordem de criação (mais antigo primeiro, depois o id) e `next_cursor` aponta a próxima; sem os dois vem a lista inteira, na ordem de sempre, com `next_cursor` nulo (compatibilidade). Com `cursor` e sem `limit`, páginas de 50. in: query name: limit required: false schema: maximum: 100 minimum: 1 type: integer Search: description: Texto de busca (case-insensitive, contém). in: query name: q required: false schema: type: string ServerId: in: path name: serverID required: true schema: format: uuid type: string responses: BadRequest: content: application/problem+json: schema: $ref: '#/components/schemas/Problem' description: Requisição inválida (JSON malformado, campo desconhecido, parâmetro inválido ou falha de validação). Conflict: content: application/problem+json: schema: $ref: '#/components/schemas/Problem' description: Conflito (unicidade ou operação concorrente em andamento). Forbidden: content: application/problem+json: schema: $ref: '#/components/schemas/Problem' description: Autenticado, mas sem permissão para a área ou a operação. NotFound: content: application/problem+json: schema: $ref: '#/components/schemas/Problem' description: Recurso não encontrado (ou fora do escopo do usuário). ServiceUnavailable: content: application/problem+json: schema: $ref: '#/components/schemas/Problem' description: Dependência (gateway de pagamento, consulta de CEP) temporariamente indisponível. TooManyRequests: content: application/problem+json: schema: $ref: '#/components/schemas/Problem' description: | Limite de requisições excedido. `Retry-After` diz em quantos segundos cabe a próxima requisição; os limites de taxa também mandam `RateLimit-Policy` e `RateLimit` (draft-ietf-httpapi-ratelimit-headers). headers: RateLimit: $ref: '#/components/headers/RateLimit' RateLimit-Policy: $ref: '#/components/headers/RateLimitPolicy' Retry-After: $ref: '#/components/headers/RetryAfter' Unauthorized: content: application/problem+json: schema: $ref: '#/components/schemas/Problem' description: Não autenticado (token ausente, inválido ou expirado; credenciais inválidas). UnprocessableEntity: content: application/problem+json: schema: $ref: '#/components/schemas/Problem' description: Entrada válida, mas viola uma regra de negócio. schemas: CardType: enum: - credit_card type: string ChargeStatus: enum: - pending - confirmed - received - overdue - refunding - refunded - chargeback - cancelled - failed type: string CloudAssignInput: additionalProperties: false properties: server_id: description: Servidor da mesma região, ou null para deixar o IP sem servidor. format: uuid nullable: true type: string required: - server_id type: object CloudCatalog: additionalProperties: false properties: available: description: O motor de provisionamento está configurado. type: boolean internal_account: description: Conta de uso interno - contrata sem cartão e sem cobrança. type: boolean os_images: items: $ref: '#/components/schemas/CloudOSImage' type: array plans: items: $ref: '#/components/schemas/CloudCatalogPlan' type: array simulated: description: O motor é simulado (ambiente de validação) e nenhum servidor é instalado de verdade. type: boolean sites: items: $ref: '#/components/schemas/CloudSite' type: array verified: description: A conta tem cartão salvo ou é de uso interno (não precisa). type: boolean required: - sites - plans - os_images - verified - available - simulated - internal_account type: object CloudCatalogPlan: additionalProperties: false properties: active: type: boolean bandwidth_mbps: description: Limite de banda; 0 é a porta inteira. type: integer boot_slot: description: 'Disco pelo qual o servidor inicia (id de disk_slots): o sistema precisa incluí-lo, sozinho ou num pool de discos iguais. As máquinas iniciam em BIOS legado só pelo primeiro disco. Ausente quando qualquer disco inicia o servidor.' type: string code: type: string cores: type: integer cpu: type: string cpu_threads: type: integer created_at: format: date-time type: string disk_slots: description: Os discos do plano um a um, onde o sistema pode ser instalado. items: $ref: '#/components/schemas/CloudDiskSlot' type: array disks: description: Discos mínimos que a máquina precisa ter. items: $ref: '#/components/schemas/CloudDisk' type: array id: format: uuid type: string memory_gb: type: integer monthly_cents: format: int64 type: integer name: type: string port_speed_mbps: description: Velocidade da porta entregue. type: integer product_id: format: uuid nullable: true type: string raid1: description: O plano tem dois discos iguais, então o servidor pode espelhá-los (RAID 1). type: boolean stock: additionalProperties: type: integer description: Máquinas em estoque por id de local. type: object traffic_tb: description: Tráfego mensal em TB; 0 é ilimitado. type: integer required: - id - code - name - cpu - cores - cpu_threads - memory_gb - disks - port_speed_mbps - bandwidth_mbps - traffic_tb - monthly_cents - product_id - active - created_at - raid1 - disk_slots - stock type: object CloudConsoleCursor: additionalProperties: false properties: col: type: integer hidden: type: boolean row: type: integer required: - row - col - hidden type: object CloudConsoleInput: additionalProperties: false properties: input: items: additionalProperties: false properties: key: description: Enter, Tab, Esc, Backspace, Delete, Insert, Home, End, PageUp, PageDown, Up, Down, Left, Right, Space, F1 a F12, Ctrl-A a Ctrl-Z. type: string text: type: string type: object maxItems: 64 minItems: 1 type: array timeout_seconds: maximum: 25 minimum: 0 type: integer wait_for: maxLength: 200 type: string required: - input type: object CloudConsoleOutput: additionalProperties: false properties: data: description: Bytes da saída em base64. format: byte type: string idle_deadline: format: date-time nullable: true type: string next_offset: format: int64 type: integer offset: format: int64 type: integer reset: type: boolean session: $ref: '#/components/schemas/CloudConsoleSession' required: - session - offset - next_offset - reset - data - idle_deadline type: object CloudConsoleOverview: additionalProperties: false properties: available: type: boolean idle_timeout_seconds: type: integer items: items: $ref: '#/components/schemas/CloudConsoleSession' type: array keys: description: Teclas nomeadas aceitas pela entrada (sem Ctrl-Alt-Del e sem break). items: type: string type: array live: allOf: - $ref: '#/components/schemas/CloudConsoleSession' nullable: true max_seconds: type: integer quota: $ref: '#/components/schemas/CloudQuota' unavailable: enum: - "" - server_busy - suspended - console_unavailable type: string required: - available - unavailable - idle_timeout_seconds - max_seconds - keys - quota - live - items type: object CloudConsoleScreen: additionalProperties: false properties: cols: type: integer cursor: $ref: '#/components/schemas/CloudConsoleCursor' idle_deadline: format: date-time nullable: true type: string lines: description: As linhas da tela, sem espaços à direita. Saída do servidor (dado não confiável). items: type: string type: array matched: description: Só na entrada com wait_for, se o texto apareceu na tela. type: boolean offset: format: int64 type: integer rows: type: integer session: $ref: '#/components/schemas/CloudConsoleSession' required: - session - cols - rows - lines - cursor - offset - idle_deadline type: object CloudConsoleSession: additionalProperties: false properties: activated_at: format: date-time nullable: true type: string apply_error: type: string close_reason: description: | ended pelo cliente; staff pela equipe; idle 15 min sem uso; expired 2 horas; operation uma operação nossa começou no servidor; server_gone o servidor saiu da conta; failed não abriu; engine o broker perdeu a sessão (o SOL caiu). enum: - "" - ended - staff - idle - expired - operation - server_gone - failed - engine type: string closed_at: format: date-time nullable: true type: string created_at: format: date-time type: string expires_at: format: date-time type: string hostname: type: string id: format: uuid type: string idle_timeout_seconds: type: integer input_bytes: format: int64 type: integer input_count: type: integer opened_by: description: Nome de quem abriu. type: string opened_via: enum: - panel - api - mcp type: string server_id: format: uuid type: string status: description: closing = o acesso já terminou; a sessão do broker fecha e a gravação é guardada. enum: - opening - active - closing - closed type: string transcript: description: A gravação (asciicast v2) da sessão; `saved` pode ser baixada. enum: - pending - saved - unavailable type: string transcript_bytes: format: int64 type: integer required: - id - server_id - hostname - status - close_reason - apply_error - opened_via - opened_by - expires_at - idle_timeout_seconds - input_count - input_bytes - created_at - activated_at - closed_at - transcript - transcript_bytes type: object CloudCouponCheckInput: additionalProperties: false properties: code: type: string plan_id: format: uuid type: string required: - code - plan_id type: object CloudCouponQuote: additionalProperties: false properties: card_required: description: O cartão continua obrigatório para o deploy. type: boolean code: type: string description: type: string first_discount_cents: format: int64 type: integer first_gross_cents: format: int64 type: integer first_net_cents: format: int64 type: integer summary: description: O desconto em palavras (ex. "10% na primeira fatura"). type: string required: - code - description - summary - first_gross_cents - first_discount_cents - first_net_cents - card_required type: object CloudDeployInput: additionalProperties: false properties: coupon_code: description: Cupom de desconto digitado no checkout; inválido recusa a compra com 422 (campo coupon_code). type: string description: maxLength: 500 type: string disk_layout: description: 'Legado (use storage, que tem precedência): single é o disco padrão (ext4 no Proxmox VE) e raid1 o espelho do primeiro par de discos iguais (ZFS RAID 1 no Proxmox VE), se o plano e o sistema permitem. Sem storage nem disk_layout: o Proxmox VE vai em ZFS RAID 1 no par de discos iguais com o disco de boot (ou ZFS num disco) e um sistema de imagem no menor disco que não seja HDD.' enum: - single - raid1 type: string end_customer_ref: description: | Id do cliente final no sistema do revendedor: agrupa servidores, não é único. Id opaco, sem dado pessoal (nem nome nem CPF), com a mesma regra do `external_id`. Vazio ou ausente: sem referência. pattern: ^([A-Za-z0-9._:-]{1,128})?$ type: string external_id: description: | Id do pedido no sistema do revendedor: único na conta entre os servidores não removidos. Id opaco, sem dado pessoal (nem nome nem CPF): de 1 a 128 caracteres entre letras sem acento, números e `. _ : -`. Repetido na conta: 409 `cloud.external_id_taken` com `existing_id`. Um servidor em remoção ainda ocupa o valor até a remoção terminar (status `deleted`). Vazio ou ausente: servidor sem referência. pattern: ^([A-Za-z0-9._:-]{1,128})?$ type: string hostname: type: string os_image_id: format: uuid type: string password: description: Gera uma senha a cada instalação e a envia por e-mail a quem pediu. type: boolean plan_id: format: uuid type: string site_id: format: uuid type: string ssh_key_ids: items: format: uuid type: string type: array storage: $ref: '#/components/schemas/CloudStorageInput' tags: items: pattern: ^[a-z0-9]([a-z0-9_.:=/-]{0,62}[a-z0-9])?$ type: string maxItems: 20 type: array user_data: description: | Documento do cloud-init: a primeira linha é `#cloud-config` (YAML com um mapa de chaves) ou um `#!` de script, em UTF-8, até 64 KiB; pode vir em base64. Roda no primeiro boot de cada instalação, depois da configuração da plataforma: no `#cloud-config`, o que a plataforma define (login, senha, `disable_root`, `ssh_pwauth`) prevalece e as suas listas (`runcmd`, `packages`, `write_files`, `users`…) são acrescentadas às nossas; um script roda depois do script de primeiro boot do sistema. Hostname, chaves e rede vêm dos metadados. Guardado cifrado; a API devolve só `user_data_sha256` e `user_data_size`. Só sistemas por imagem (422 `cloud.user_data_unsupported` no instalador do Proxmox VE e em "Sem sistema"). Muda só com a reinstalação. maxLength: 90000 type: string required: - site_id - plan_id - os_image_id - hostname type: object CloudDisk: additionalProperties: false properties: count: type: integer model: type: string size_gb: type: integer type: enum: - nvme - ssd - hdd type: string required: - count - type - size_gb type: object CloudDiskSlot: additionalProperties: false description: Um disco do plano, um a um. O id fica igual enquanto os discos do plano não mudam. properties: id: description: '--, por exemplo ssd-128-2: o segundo SSD de 128 GB do plano.' example: ssd-128-2 type: string model: type: string size_gb: type: integer type: enum: - nvme - ssd - hdd type: string required: - id - type - size_gb - model type: object CloudElasticIP: additionalProperties: false properties: address: description: IPv4 público (/32). type: string apply_error: description: Última resposta do gateway a uma mudança que não passou ("" quando não há). type: string contract_id: format: uuid nullable: true type: string created_at: format: date-time type: string first_invoice_id: format: uuid nullable: true type: string id: format: uuid type: string monthly_cents: format: int64 type: integer region: $ref: '#/components/schemas/CloudRegionRef' server_hostname: type: string server_id: description: Servidor para onde o IP vai (null = sem servidor). format: uuid nullable: true type: string status: description: pending_payment = primeira fatura em cobrança; configuring/moving/releasing = mudança indo para o gateway; failed = o gateway recusou ou não aplicou a tempo (veja apply_error). enum: - pending_payment - configuring - active - moving - releasing - failed type: string suspended_at: description: Rota retirada por falta de pagamento; o endereço continua reservado e a rota volta quando a fatura é paga. format: date-time nullable: true type: string required: - id - address - region - server_id - server_hostname - status - apply_error - monthly_cents - contract_id - first_invoice_id - created_at - suspended_at type: object CloudElasticIPInput: additionalProperties: false properties: region_id: format: uuid type: string server_id: format: uuid nullable: true type: string required: - region_id type: object CloudElasticIPList: additionalProperties: false properties: items: items: $ref: '#/components/schemas/CloudElasticIP' type: array next_cursor: description: Cursor da próxima página (nulo na última e sem paginação; ausente nas listas da equipe). nullable: true type: string required: - items type: object CloudIPMIAccess: additionalProperties: false properties: address: description: IP da IPMI, alcançável só pelo túnel. type: string password: description: | Só enquanto a sessão está viva e só para o cliente que opera os servidores (responsável); "" nos outros casos, inclusive para o usuário de consulta. type: string url: type: string username: description: Conta da IPMI criada para a sessão (papel Operator). type: string required: - address - url - username - password type: object CloudIPMIVPNInput: additionalProperties: false properties: duration_minutes: enum: - 60 - 240 - 480 type: integer public_key: description: Chave pública WireGuard do cliente (base64 de 32 bytes, como a saída de `wg pubkey`). type: string required: - duration_minutes - public_key type: object CloudIPMIVPNOverview: additionalProperties: false properties: available: type: boolean durations: description: Durações possíveis, em minutos. items: type: integer type: array items: items: $ref: '#/components/schemas/CloudIPMIVPNSession' type: array ports: items: $ref: '#/components/schemas/CloudIPMIVPNPort' type: array quota: $ref: '#/components/schemas/CloudQuota' unavailable: enum: - "" - server_busy - region_without_vpn - machine_without_bmc - suspended type: string required: - available - unavailable - durations - quota - ports - items type: object CloudIPMIVPNPort: additionalProperties: false properties: port: type: integer protocol: enum: - tcp - udp type: string use: type: string required: - protocol - port - use type: object CloudIPMIVPNSession: additionalProperties: false properties: activated_at: format: date-time nullable: true type: string apply_error: description: Última resposta da IPMI ou do OPNsense a uma mudança que não passou ("" quando não há). type: string bmc: $ref: '#/components/schemas/CloudIPMIAccess' close_reason: description: Por que encerrou (ou está encerrando). operation = uma operação nossa (deploy, reinstalação, limpeza ou exclusão) começou no servidor; suspended = o servidor foi desligado por falta de pagamento. enum: - "" - ended - staff - expired - server_gone - failed - operation - suspended type: string closed_at: format: date-time nullable: true type: string created_at: format: date-time type: string duration_minutes: type: integer expires_at: format: date-time type: string hostname: type: string id: format: uuid type: string server_id: format: uuid type: string status: description: opening = conta da IPMI e peer sendo criados; closing = peer, regras e conta sendo removidos. enum: - opening - active - closing - closed type: string wireguard: $ref: '#/components/schemas/CloudWireGuardPeer' required: - id - server_id - hostname - status - close_reason - apply_error - duration_minutes - expires_at - created_at - activated_at - closed_at - bmc - wireguard type: object CloudNetworkMember: additionalProperties: false properties: address: description: Endereço sugerido ao servidor na sub-rede. type: string apply_error: description: Última resposta do switch a uma mudança que não passou ("" quando nenhuma). type: string hostname: type: string id: format: uuid type: string server_id: format: uuid type: string status: description: | A porta do switch do servidor, configurada pelo job `cloud-network`: connecting = a VLAN indo para a porta; failed = o switch recusou ou não aplicou a tempo; removing = a VLAN saindo da porta. enum: - connecting - active - failed - removing type: string required: - id - server_id - hostname - address - status - apply_error type: object CloudNetworkOverview: additionalProperties: false properties: quotas: additionalProperties: false properties: elastic_ips: $ref: '#/components/schemas/CloudQuota' firewalls: $ref: '#/components/schemas/CloudQuota' private_networks: $ref: '#/components/schemas/CloudQuota' required: - elastic_ips - private_networks - firewalls type: object regions: items: $ref: '#/components/schemas/CloudNetworkRegion' type: array verified: description: A conta tem cartão salvo (IP elástico com preço exige). type: boolean required: - regions - quotas - verified type: object CloudNetworkRegion: additionalProperties: false properties: code: type: string elastic_ip_monthly_cents: description: Preço mensal do próximo IP elástico da região. format: int64 type: integer elastic_ips: description: IPs elásticos funcionam na região (o OPNsense é o gateway dos servidores) e há endereço livre. type: boolean firewalls: description: O firewall funciona na região (o OPNsense é o gateway dos servidores). type: boolean id: format: uuid type: string name: type: string private_networks: description: Redes privadas funcionam na região (a plataforma configura o switch) e o bloco de rede privada tem espaço. type: boolean routed_by_gateway: description: | A rede da região faz IPs elásticos e firewall terem efeito (o OPNsense é o gateway dos servidores e está cadastrado), haja ou não endereço livre agora. Mover um IP elástico pede só isso. type: boolean servers: description: Servidores da conta na região. type: integer switch_automated: description: | A plataforma configura as portas do switch da região, então redes privadas têm efeito, haja ou não espaço no bloco agora. Colocar um servidor numa rede pede só isso. type: boolean required: - id - code - name - servers - elastic_ips - elastic_ip_monthly_cents - firewalls - private_networks - routed_by_gateway - switch_automated type: object CloudOSImage: additionalProperties: false properties: code: type: string family: type: string id: format: uuid type: string install_method: description: image grava uma imagem de disco; installer inicia o instalador oficial do sistema (Proxmox VE); none não instala nada (o cliente instala o próprio sistema pela IPMI). enum: - image - installer - none type: string login_user: description: Usuário de acesso ao sistema instalado. type: string name: type: string password_required: description: O sistema é usado com senha (painel web do Proxmox VE). type: boolean raid1: description: O sistema instala num espelho (RAID 1) de dois discos iguais. type: boolean storage: $ref: '#/components/schemas/CloudStorageCaps' version: type: string required: - id - code - name - family - version - login_user - password_required - install_method - raid1 - storage type: object CloudOperation: additionalProperties: false description: | Uma operação de um servidor, o contrato de espera da API: consulte até `status` ser `succeeded` ou `failed` (terminais). `pending` é a operação registrada que o motor ainda não começou; `running`, uma etapa em andamento. properties: error: description: Resumo da etapa que falhou (vazio sem falha). type: string finished_at: format: date-time nullable: true type: string id: format: uuid type: string requested_at: format: date-time type: string server_id: format: uuid type: string started_at: format: date-time nullable: true type: string status: enum: - pending - running - succeeded - failed type: string steps: items: $ref: '#/components/schemas/CloudStep' type: array type: enum: - deploy - reinstall - wipe - delete type: string required: - id - server_id - type - status - steps - error - requested_at - started_at - finished_at type: object CloudOperationList: additionalProperties: false properties: items: items: $ref: '#/components/schemas/CloudOperation' type: array next_cursor: nullable: true type: string required: - items - next_cursor type: object CloudPlan: additionalProperties: false properties: active: type: boolean bandwidth_mbps: description: Limite de banda; 0 é a porta inteira. type: integer boot_slot: description: 'Disco pelo qual o servidor inicia (id de disk_slots): o sistema precisa incluí-lo, sozinho ou num pool de discos iguais. As máquinas iniciam em BIOS legado só pelo primeiro disco. Ausente quando qualquer disco inicia o servidor.' type: string code: type: string cores: type: integer cpu: type: string cpu_threads: type: integer created_at: format: date-time type: string disk_slots: description: Os discos do plano um a um, onde o sistema pode ser instalado. items: $ref: '#/components/schemas/CloudDiskSlot' type: array disks: description: Discos mínimos que a máquina precisa ter. items: $ref: '#/components/schemas/CloudDisk' type: array id: format: uuid type: string memory_gb: type: integer monthly_cents: format: int64 type: integer name: type: string port_speed_mbps: description: Velocidade da porta entregue. type: integer product_id: format: uuid nullable: true type: string raid1: description: O plano tem dois discos iguais, então o servidor pode espelhá-los (RAID 1). type: boolean traffic_tb: description: Tráfego mensal em TB; 0 é ilimitado. type: integer required: - id - code - name - cpu - cores - cpu_threads - memory_gb - disks - port_speed_mbps - bandwidth_mbps - traffic_tb - monthly_cents - product_id - active - created_at - raid1 - disk_slots type: object CloudPrivateNetwork: additionalProperties: false properties: apply_error: type: string created_at: format: date-time type: string description: type: string id: format: uuid type: string region: $ref: '#/components/schemas/CloudRegionRef' servers: items: $ref: '#/components/schemas/CloudNetworkMember' type: array status: enum: - configuring - active - deleting - failed type: string subnet: description: Sub-rede da rede (ex. 10.240.0.0/24). type: string vlan_id: description: VID que o cliente configura no sistema (802.1Q). type: integer required: - id - region - vlan_id - subnet - description - status - apply_error - created_at - servers type: object CloudPrivateNetworkInput: additionalProperties: false properties: description: description: Até 80 caracteres. type: string region_id: format: uuid type: string required: - region_id - description type: object CloudPrivateNetworkList: additionalProperties: false properties: items: items: $ref: '#/components/schemas/CloudPrivateNetwork' type: array next_cursor: description: Cursor da próxima página (nulo na última e sem paginação; ausente nas listas da equipe). nullable: true type: string required: - items type: object CloudPrivateNetworkUpdate: additionalProperties: false properties: description: type: string required: - description type: object CloudQuota: additionalProperties: false properties: limit: type: integer used: type: integer required: - used - limit type: object CloudRAIDLevel: additionalProperties: false description: 'Um nível de RAID oferecido e quantos discos ele usa. Mínimos do ZFS conforme pve.proxmox.com/wiki/ZFS_on_Linux: RAID 1 a partir de 2, RAID 10 a partir de 4 e em pares, RAIDZ-1 de 3, RAIDZ-2 de 4, RAIDZ-3 de 5; RAID 0 a partir de 2.' properties: even_disks: description: O nível usa discos em pares (RAID 10). type: boolean filesystems: description: Sistemas de arquivos que oferecem o nível; vazio nos sistemas de imagem. items: enum: - zfs - ext4 - xfs type: string type: array level: enum: - single - raid0 - raid1 - raid10 - raidz-1 - raidz-2 - raidz-3 type: string max_disks: description: 0 é sem limite. type: integer min_disks: type: integer required: - level - filesystems - min_disks - max_disks - even_disks type: object CloudRegionRef: additionalProperties: false properties: code: type: string id: format: uuid type: string name: type: string required: - id - code - name type: object CloudSSHKey: additionalProperties: false properties: created_at: format: date-time type: string fingerprint: type: string id: format: uuid type: string name: type: string public_key: type: string required: - id - name - fingerprint - public_key - created_at type: object CloudSSHKeyInput: additionalProperties: false properties: name: type: string public_key: type: string required: - public_key type: object CloudSSHKeyList: additionalProperties: false properties: items: items: $ref: '#/components/schemas/CloudSSHKey' type: array next_cursor: description: Cursor da próxima página (nulo na última e sem paginação). nullable: true type: string required: - items type: object CloudSSHKeyUpdate: additionalProperties: false properties: name: description: Novo nome (até 80 caracteres). A chave em si não muda; para outra chave, cadastre uma nova. type: string required: - name type: object CloudServer: additionalProperties: false properties: cancel_at: description: Último dia da assinatura cancelada (fim do período pago); depois dele a máquina volta ao estoque. format: date nullable: true type: string contract_id: format: uuid nullable: true type: string created_at: format: date-time type: string description: description: Descrição livre do cliente (até 500 caracteres). type: string disk_layout: description: Legado (veja storage) - raid1 quando a última instalação é um RAID 1, senão single. enum: - single - raid1 type: string end_customer_ref: description: | Id do cliente final no sistema do revendedor: agrupa servidores, não é único. Id opaco, sem dado pessoal (nem nome nem CPF), com a mesma regra do `external_id`. Nulo quando não informado. nullable: true type: string external_id: description: | Id do pedido no sistema do revendedor: único na conta entre os servidores não removidos. Id opaco, sem dado pessoal (nem nome nem CPF): de 1 a 128 caracteres entre letras sem acento, números e `. _ : -`. Nulo quando não informado. nullable: true type: string first_invoice_id: description: Fatura cobrada na compra. format: uuid nullable: true type: string hostname: type: string id: format: uuid type: string ipv4: type: string ipv4_gateway: type: string ipv4_prefix: type: integer monthly_cents: description: 'O que o servidor custa por mês: a recorrência do contrato dele (com reajustes), não o preço atual do plano, que vale só para servidores novos.' format: int64 type: integer operation: enum: - deploy - reinstall - wipe - delete - "" type: string operation_id: description: A última operação do servidor (deploy, reinstall, wipe ou delete); acompanhe por `GET /cloud/operations/{id}`. format: uuid nullable: true type: string os: $ref: '#/components/schemas/CloudOSImage' password_login: description: Cada instalação gera uma senha e a envia por e-mail. type: boolean plan: $ref: '#/components/schemas/CloudPlan' power: enum: - "on" - "off" - unknown type: string site: $ref: '#/components/schemas/CloudSite' ssh_keys: description: Quantidade de chaves instaladas no último deploy. type: integer status: enum: - pending_payment - deploying - active - failed - wiping - empty - deleting - deleted type: string steps: items: $ref: '#/components/schemas/CloudStep' type: array storage: $ref: '#/components/schemas/CloudStorage' suspended_at: description: | Desligado por falta de pagamento, com os dados mantidos; volta a ser ligado sozinho quando a fatura é paga. Enquanto isso, power, reinstalação, apagar, cancelar e VPN da IPMI respondem 409 `cloud.server_suspended`. format: date-time nullable: true type: string tags: description: Tags do cliente, em ordem alfabética e sem repetição. items: type: string type: array terminated_at: description: | Encerrado na hora pela equipe. O cliente continua vendo a página do servidor (o aviso leva até ela) mesmo depois de removido, só com o texto fixo da categoria. format: date-time nullable: true type: string termination_category: description: Por que a equipe encerrou o servidor (vazio quando não foi encerrado pela equipe). enum: - "" - abuse - fraud - customer_request - internal_test - delinquency - other type: string user_data_sha256: description: SHA-256 (hex) do user data das instalações; o conteúdo nunca é devolvido. Nulo sem user data. nullable: true type: string user_data_size: description: Tamanho do user data em bytes (0 sem user data). type: integer required: - id - hostname - status - power - site - plan - os - storage - disk_layout - ipv4 - ipv4_prefix - ipv4_gateway - ssh_keys - password_login - contract_id - monthly_cents - first_invoice_id - cancel_at - suspended_at - operation - steps - created_at - terminated_at - termination_category type: object CloudServerList: additionalProperties: false properties: items: items: $ref: '#/components/schemas/CloudServer' type: array next_cursor: description: Cursor da próxima página (nulo na última e sem paginação). nullable: true type: string required: - items type: object CloudServerRef: additionalProperties: false properties: server_id: format: uuid type: string required: - server_id type: object CloudServerUpdate: additionalProperties: false description: | Campos ausentes ficam como estão (JSON merge patch, RFC 7396); `tags` vazio, `description` vazia e `external_id` ou `end_customer_ref` vazios limpam. properties: description: maxLength: 500 type: string end_customer_ref: description: | Id do cliente final no sistema do revendedor: agrupa servidores, não é único. Id opaco, sem dado pessoal (nem nome nem CPF), com a mesma regra do `external_id`. Vazio limpa. pattern: ^([A-Za-z0-9._:-]{1,128})?$ type: string external_id: description: | Id do pedido no sistema do revendedor: único na conta entre os servidores não removidos. Id opaco, sem dado pessoal (nem nome nem CPF): de 1 a 128 caracteres entre letras sem acento, números e `. _ : -`. Vazio limpa; o de outro servidor da conta responde 409 `cloud.external_id_taken` com `existing_id`. pattern: ^([A-Za-z0-9._:-]{1,128})?$ type: string hostname: description: | Mesma regra do deploy (letras minúsculas, números e hífen, até 63). Muda só o nome na plataforma (listas, e-mails, a confirmação das ações destrutivas); o sistema mantém o hostname da instalação até a próxima reinstalação, que usa o novo. O título do contrato não muda. type: string tags: items: pattern: ^[a-z0-9]([a-z0-9_.:=/-]{0,62}[a-z0-9])?$ type: string maxItems: 20 type: array type: object CloudSite: additionalProperties: false properties: active: type: boolean city: type: string code: type: string country: type: string created_at: format: date-time type: string id: format: uuid type: string name: type: string region_id: description: Região do local; os IPs das máquinas vêm dos blocos dela. format: uuid type: string required: - id - region_id - code - name - city - country - active - created_at type: object CloudStep: additionalProperties: false properties: finished_at: format: date-time nullable: true type: string message: type: string name: enum: - prepare - erase - install - configure - reboot - ready - release type: string started_at: format: date-time nullable: true type: string status: enum: - pending - running - done - failed type: string required: - name - status - message - started_at - finished_at type: object CloudStorage: additionalProperties: false description: Onde a última instalação pôs o sistema. properties: disks: items: $ref: '#/components/schemas/CloudDiskSlot' type: array filesystem: description: Vazio nos sistemas de imagem (o sistema de arquivos é o da imagem). enum: - zfs - ext4 - xfs - "" type: string raid: enum: - single - raid0 - raid1 - raid10 - raidz-1 - raidz-2 - raidz-3 type: string summary: example: ZFS RAID 1 em 2× SSD 128 GB type: string required: - disks - filesystem - raid - summary type: object CloudStorageCaps: additionalProperties: false description: O que o cliente escolhe nos discos deste sistema. O instalador do Proxmox VE grava ZFS (padrão, recomendado), ext4 ou XFS; ext4 e XFS vão num disco, o ZFS num disco ou nos níveis de RAID. Um sistema de imagem vai num disco (o sistema de arquivos é o da imagem) ou, quando o motor tem a versão dele para espelho, em RAID 1 de dois discos iguais. properties: default_filesystem: enum: - zfs - ext4 - xfs - "" type: string filesystems: description: Sistemas de arquivos que o instalador grava; vazio nos sistemas de imagem. items: enum: - zfs - ext4 - xfs type: string type: array levels: items: $ref: '#/components/schemas/CloudRAIDLevel' type: array required: - filesystems - default_filesystem - levels type: object CloudStorageInput: additionalProperties: false description: Os discos do plano (ids de disk_slots), o sistema de arquivos e o nível de RAID. Um pool usa discos iguais (mesmo tipo e tamanho); ext4 e XFS usam um disco; um sistema de imagem usa um disco, ou dois em RAID 1 quando oferece; cada nível do ZFS pede o seu mínimo de discos; a escolha inclui o boot_slot do plano, quando ele existe. Os discos fora da escolha ficam livres. properties: disks: items: type: string type: array filesystem: description: zfs (padrão no Proxmox VE), ext4 ou xfs; vazio nos sistemas de imagem. type: string raid: description: single, raid0, raid1, raid10, raidz-1, raidz-2 ou raidz-3; obrigatório no ZFS com mais de um disco. type: string required: - disks type: object CloudWireGuardPeer: additionalProperties: false properties: address: description: Endereço do cliente no túnel (/32). type: string allowed_ips: description: Só a IPMI do servidor (/32). type: string config: description: Arquivo do wg-quick com `` no lugar da chave privada ("" até ativa). type: string endpoint: description: host:porta do WireGuard do OPNsense. type: string keepalive: type: integer public_key: description: Chave pública do cliente. type: string server_public_key: description: Chave pública do OPNsense ("" até a sessão ficar ativa). type: string required: - address - public_key - server_public_key - endpoint - allowed_ips - keepalive - config type: object DiscountSource: description: | contract - desconto da equipe; coupon - cupom; internal - uso interno (conta interna); minimum - saldo abaixo do mínimo de cobrança (R$ 5,00) dispensado. enum: - contract - coupon - internal - minimum type: string Gateway: description: | Como a fatura é recebida. `manual` = Pix direto na conta da empresa, baixado por conciliação ou manualmente. `stripe` e `asaas` significam "cobrada num gateway" e aceitam Pix, boleto e cartão: cada cobrança nova sai pelo provedor configurado para a forma (`PIX_PROVIDER`, `BOLETO_PROVIDER`, `CARD_PROVIDER`; veja `GET /public/card-config`), a cobrança já emitida termina no provedor que a emitiu e um cartão salvo é sempre cobrado no provedor que o salvou. Contratos novos nascem com o provedor configurado (Stripe quando ela está configurada). enum: - asaas - stripe - nupay - manual type: string InvoiceStatus: description: | `courtesy`: fatura de conta interna (cortesia), com 100% de desconto "Uso interno"; `no_charge`: descontos cobriram o valor todo. Nas duas não há nada a pagar: nunca vão ao gateway, à NFS-e nem à inadimplência. enum: - open - paid - overdue - cancelled - refunded - courtesy - no_charge type: string LateTerms: additionalProperties: false description: | Multa e juros por atraso, combinados no contrato, em pontos-base (200 = 2,00%). A multa incide uma vez; os juros são ao mês, pro rata die. Zero = sem multa/juros. Pix e boleto do Asaas levam os termos; cobranças emitidas após o vencimento e o Pix direto já incluem o que acumulou. properties: fee_bps: description: Multa (% do valor), em pontos-base. maximum: 1000 minimum: 0 type: integer interest_bps: description: Juros ao mês, em pontos-base. maximum: 1000 minimum: 0 type: integer policy: description: | Como os termos se aplicam, definido pelo sistema (ignorado nas requisições). `standard`: multa e juros desde o dia seguinte ao vencimento, o Asaas soma em Pix e boleto. `cloud` (contratos de servidores e IPs elásticos): sem multa nem juros até o 15º dia após o vencimento (dia útil); depois, juros desde o dia seguinte ao vencimento e multa só em Pix e boleto (nunca no cartão), calculados pela plataforma; o Asaas não soma nada. enum: - standard - cloud readOnly: true type: string required: - fee_bps - interest_bps type: object Me: additionalProperties: false properties: permissions: items: $ref: '#/components/schemas/Permission' type: array user: $ref: '#/components/schemas/User' required: - user - permissions type: object Month: description: Mês no formato `YYYY-MM`. example: 2026-10 pattern: ^\d{4}-(0[1-9]|1[0-2])$ type: string PageMeta: additionalProperties: false properties: limit: type: integer offset: type: integer total: format: int64 type: integer required: - limit - offset - total type: object PaymentMethod: additionalProperties: false description: Cartão tokenizado. properties: brand: type: string created_at: format: date-time type: string customer_id: format: uuid type: string expiry_month: type: integer expiry_year: type: integer holder_name: type: string id: format: uuid type: string is_default: type: boolean last4: type: string provider: $ref: '#/components/schemas/Gateway' type: $ref: '#/components/schemas/CardType' required: - id - customer_id - provider - type - brand - last4 - holder_name - expiry_month - expiry_year - is_default - created_at type: object PaymentMethodList: additionalProperties: false properties: data: items: $ref: '#/components/schemas/PaymentMethod' type: array required: - data type: object PaymentMethodType: description: Forma de pagamento. enum: - pix - boleto - credit_card type: string Permission: enum: - customers:read - customers:write - contracts:read - contracts:write - billing:read - billing:write - finance:read - finance:write - reports:read - users:manage - jobs:run - portal:read - portal:payments - cloud:read - cloud:write - cloud:operate - support:read - support:write - support:manage - portal:support - discounts:write - discounts:approve - customers:internal - cloud:terminate - apidocs:read type: string PortalCardAction: additionalProperties: false description: | Pagamento com cartão esperando o cliente confirmar (o banco emissor pediu autenticação 3D Secure). A página busca `GET .../card-action` e confirma com o Stripe.js. properties: amount_cents: format: int64 type: integer charge_id: format: uuid type: string required: - charge_id - amount_cents type: object PortalCharge: additionalProperties: false description: Cobrança como vista pelo cliente no portal. properties: action_required: description: Pagamento com cartão esperando o cliente confirmar (3D Secure pedido pelo banco). type: boolean amount_cents: format: int64 type: integer automatic: description: Debitada por Pix Automático no vencimento (não há o que pagar manualmente). type: boolean bank_slip_url: description: PDF do boleto. No link público, só enquanto a fatura e o boleto estão em aberto. type: string boleto_line: description: Linha digitável do boleto (vazia quando o provedor não a informa). No link público, só com o boleto em aberto. type: string confirmation_business_days: description: Boleto pago é confirmado em até N dias úteis (1 na Stripe, 3 no Asaas); 0 nas demais formas. type: integer due_date: format: date type: string expires_at: description: Até quando o Pix ou o boleto pode ser pago; nulo quando continua pagável depois do vencimento. format: date-time nullable: true type: string id: format: uuid type: string method: $ref: '#/components/schemas/PaymentMethodType' paid_at: format: date-time nullable: true type: string status: $ref: '#/components/schemas/ChargeStatus' required: - id - method - status - amount_cents - due_date - paid_at - bank_slip_url type: object PortalInvoice: additionalProperties: false description: Fatura como vista pelo cliente no portal (sem taxas, ids internos ou erros do gateway). properties: amount_cents: format: int64 type: integer amount_due_cents: description: Valor para pagar hoje (com multa e juros quando em atraso); 0 se a fatura não está em aberto. format: int64 type: integer cancelled_at: format: date-time nullable: true type: string card_action: allOf: - $ref: '#/components/schemas/PortalCardAction' description: Pagamento com cartão esperando o cliente confirmar (3D Secure); nulo quando não há. nullable: true charges: description: Presente apenas no detalhe e quando há cobranças acionáveis (exclui canceladas e recusadas). items: $ref: '#/components/schemas/PortalCharge' type: array competence: description: Primeiro dia do mês de competência (`YYYY-MM-01`). format: date type: string contract_id: format: uuid nullable: true type: string description: type: string discount_cents: format: int64 type: integer due_date: format: date type: string gateway: $ref: '#/components/schemas/Gateway' gross_amount_cents: description: Valor cheio (bruto); amount_cents é o que se paga. format: int64 type: integer id: format: uuid type: string late_terms: $ref: '#/components/schemas/LateTerms' lines: description: Linhas de cobrança e de desconto (só no detalhe). items: $ref: '#/components/schemas/PortalInvoiceLine' type: array number: type: string paid_amount_cents: format: int64 type: integer paid_at: format: date-time nullable: true type: string payment_issue: description: Aviso ao cliente quando a última tentativa no cartão falhou; vazio caso contrário. type: string payment_method: $ref: '#/components/schemas/PaymentMethodType' payment_method_id: format: uuid nullable: true type: string payment_url: description: Link público de pagamento, para compartilhar com quem vai pagar (só no detalhe). type: string period_end: format: date nullable: true type: string period_start: format: date nullable: true type: string refunded_amount_cents: description: Devolvido ao cliente até agora (estorno parcial ou total). format: int64 type: integer refunded_at: format: date-time nullable: true type: string service_invoice: $ref: '#/components/schemas/PortalServiceInvoice' status: $ref: '#/components/schemas/InvoiceStatus' subscription_id: description: Recorrência que gerou a fatura (nula em faturas avulsas). format: uuid nullable: true type: string required: - gross_amount_cents - discount_cents - late_terms - amount_due_cents - id - number - contract_id - description - amount_cents - paid_amount_cents - refunded_amount_cents - status - due_date - competence - period_start - period_end - payment_method - gateway - payment_method_id - paid_at - cancelled_at - refunded_at - payment_issue type: object PortalInvoiceLine: additionalProperties: false properties: amount_cents: format: int64 type: integer description: type: string discount_source: $ref: '#/components/schemas/DiscountSource' kind: enum: - charge - discount type: string quantity: type: integer required: - kind - description - quantity - amount_cents type: object PortalInvoicePage: additionalProperties: false properties: data: items: $ref: '#/components/schemas/PortalInvoice' type: array meta: $ref: '#/components/schemas/PageMeta' required: - data - meta type: object PortalServiceInvoice: additionalProperties: false description: Nota fiscal da fatura como o cliente a vê. properties: cancelled: type: boolean issued_at: format: date-time nullable: true type: string number: type: string pdf_url: type: string xml_url: type: string required: - number - issued_at - pdf_url - xml_url - cancelled type: object PortalTicket: additionalProperties: false properties: can_rate: description: Resolvido por alguém há até 28 dias; pode ser avaliado. type: boolean category: $ref: '#/components/schemas/TicketCategory' closed: description: Resolvido há mais dias que `close_days`; uma mensagem nova abre um chamado de continuação. type: boolean created_at: format: date-time type: string expected: allOf: - $ref: '#/components/schemas/TicketExpectation' description: Quando a equipe deve responder (só enquanto espera a equipe). nullable: true follow_up_of: allOf: - $ref: '#/components/schemas/TicketRef' nullable: true id: format: uuid type: string last_from: enum: - customer - staff - "" type: string last_message_at: format: date-time type: string merged_into: allOf: - $ref: '#/components/schemas/TicketRef' nullable: true messages: description: A conversa (só no detalhe). items: $ref: '#/components/schemas/PortalTicketMessage' type: array number: example: SUP-000123 type: string priority: $ref: '#/components/schemas/TicketPriority' purged: description: A conversa foi apagada pelo prazo de guarda (1 ano após a resolução). type: boolean rating: allOf: - $ref: '#/components/schemas/TicketRating' nullable: true resolved_at: format: date-time nullable: true type: string server: allOf: - $ref: '#/components/schemas/TicketServer' nullable: true status: $ref: '#/components/schemas/TicketStatus' subject: type: string unread: description: Há resposta da equipe que a conta ainda não leu. type: boolean required: - id - number - subject - category - status - priority - server - last_message_at - last_from - unread - expected - closed - merged_into - follow_up_of - rating - can_rate - created_at - resolved_at - purged type: object PortalTicketList: additionalProperties: false properties: counts: additionalProperties: false description: Chamados da conta em andamento (abertos ou respondidos) e resolvidos, em todas as páginas. properties: active: format: int64 type: integer resolved: format: int64 type: integer required: - active - resolved type: object data: items: $ref: '#/components/schemas/PortalTicket' type: array meta: $ref: '#/components/schemas/PageMeta' required: - data - meta - counts type: object PortalTicketMessage: additionalProperties: false properties: attachments: items: $ref: '#/components/schemas/TicketAttachment' type: array author: enum: - customer - staff - system type: string author_name: description: Quem escreveu; toda mensagem da equipe aparece como "Equipe Halver" e as do sistema como "Halver". type: string body: type: string created_at: format: date-time type: string id: format: uuid type: string required: - id - author - author_name - body - attachments - created_at type: object Problem: additionalProperties: false description: RFC 9457 problem details. properties: code: description: Código estável, ex. `validation_failed`, `contract.invalid_transition`, `customer.not_found`. type: string detail: description: Mensagem legível (pt-BR). Omitida quando vazia. type: string errors: additionalProperties: type: string description: Mensagens por campo (em `validation_failed` e `request.invalid_json`). type: object existing_id: description: Num conflito com um registro que já existe, o id dele (ex. o servidor com o mesmo `external_id` em `cloud.external_id_taken`). format: uuid type: string request_id: description: Mesmo valor do header `X-Request-ID`. type: string status: type: integer title: description: Texto padrão do status HTTP (ex. `Bad Request`). type: string type: description: Sempre `about:blank`. type: string required: - type - title - status - code type: object Receipt: additionalProperties: false description: Comprovante de pagamento. O que ele atesta é copiado na emissão; `status` é lido da fatura a cada consulta. properties: adjustment_cents: description: Outra diferença entre o pago e o valor da fatura (negativa = desconto), ex. baixa registrada pela equipe. format: int64 type: integer amount_cents: description: Valor da fatura. format: int64 type: integer code: description: Código de validação, `XXXX-XXXX-XXXX-XXXX`. type: string description: type: string due_date: format: date type: string invoice_number: type: string issued_at: format: date-time type: string issuer_document: description: CNPJ de quem recebeu, formatado. type: string issuer_name: description: Quem recebeu (razão social). type: string late_cents: description: Multa e juros pagos além do valor da fatura (pagamento após o vencimento). format: int64 type: integer method: description: '`bank_transfer`: Pix/transferência direto na conta da empresa; `manual`: registrado pela equipe.' enum: - pix - boleto - credit_card - bank_transfer - manual type: string paid_amount_cents: format: int64 type: integer paid_at: format: date-time type: string payer_document: description: CNPJ completo ou CPF mascarado. type: string payer_name: type: string refunded_amount_cents: description: Devolvido ao pagador (estorno parcial mantém `valid`). format: int64 type: integer refunded_at: format: date-time nullable: true type: string status: enum: - valid - refunded - reverted type: string url: description: Página pública do comprovante (para o QR code e para compartilhar). type: string required: - code - url - status - refunded_at - refunded_amount_cents - issuer_name - issuer_document - payer_name - payer_document - invoice_number - description - due_date - amount_cents - late_cents - adjustment_cents - paid_amount_cents - paid_at - method - issued_at type: object Role: description: Papel do usuário. Staff - director, finance, sales, support. Portal - owner, viewer. enum: - director - finance - sales - support - owner - viewer type: string TicketAttachment: additionalProperties: false properties: content_type: enum: - image/png - image/jpeg - image/gif - image/webp - application/pdf - text/plain type: string created_at: format: date-time type: string file_name: type: string id: format: uuid type: string size_bytes: type: integer required: - id - file_name - content_type - size_bytes - created_at type: object TicketCategory: description: 'Do que se trata: vendas, suporte técnico, financeiro, bare metal ou privacidade (pedidos do titular, LGPD art. 18).' enum: - sales - technical - billing - bare_metal - privacy type: string TicketExpectation: additionalProperties: false properties: due_at: format: date-time type: string target: example: até 4 horas úteis type: string required: - due_at - target type: object TicketMessageInput: additionalProperties: false properties: attachment_ids: items: format: uuid type: string maxItems: 5 type: array body: maxLength: 5000 type: string required: - body type: object TicketOpenInput: additionalProperties: false properties: attachment_ids: items: format: uuid type: string maxItems: 5 type: array category: $ref: '#/components/schemas/TicketCategory' message: maxLength: 5000 type: string priority: $ref: '#/components/schemas/TicketPriority' server_id: description: Servidor da conta de que o chamado trata. format: uuid nullable: true type: string subject: maxLength: 150 type: string required: - subject - category - message type: object TicketPriority: description: 'Prioridade: baixa, normal, alta ou urgente (define o SLA).' enum: - low - normal - high - urgent type: string TicketRating: additionalProperties: false properties: comment: type: string rated_at: format: date-time type: string rating: enum: - good - bad type: string required: - rating - comment - rated_at type: object TicketRef: additionalProperties: false properties: id: format: uuid type: string number: type: string required: - id - number type: object TicketServer: additionalProperties: false properties: hostname: type: string id: format: uuid type: string required: - id - hostname type: object TicketStatus: description: '`open`: espera a equipe; `answered`: espera o cliente; `resolved`: resolvido.' enum: - open - answered - resolved type: string User: additionalProperties: false properties: active: type: boolean created_at: format: date-time type: string customer_id: description: Cliente do usuário do portal; `null` para staff. format: uuid nullable: true type: string email: type: string email_verified_at: description: Quando o usuário confirmou o e-mail (abriu um link ou digitou um código enviado a ele). format: date-time nullable: true type: string id: format: uuid type: string kind: $ref: '#/components/schemas/UserKind' last_login_at: format: date-time nullable: true type: string name: type: string pending_access: description: | `true` enquanto o usuário não ativou o acesso (cliente: não confirmou o e-mail; equipe: não criou a senha). type: boolean role: $ref: '#/components/schemas/Role' updated_at: format: date-time type: string required: - id - kind - role - customer_id - name - email - active - pending_access - last_login_at - created_at - updated_at - email_verified_at type: object UserKind: enum: - staff - customer type: string securitySchemes: apiToken: bearerFormat: sc_live_… description: | Token de API do cliente (`sc_live_…` em produção, `sc_test_…` nos outros ambientes), criado em Desenvolvedores › Tokens de API. Vale só nas operações com `x-token-scope`, e só com o escopo indicado ali e o papel atual de quem criou o token. Limite de 120 requisições por minuto por token (rajada de 60). scheme: bearer type: http info: contact: name: Halver url: https://docs.halver.com.br description: |- API da Halver para gerenciar servidores bare metal, chaves SSH, redes privadas, IPs elásticos, faturas e chamados da sua conta. Autentique com um token de API (`Authorization: Bearer sc_live_…`) criado em Desenvolvedores › Tokens de API no painel (dash.halver.com.br). Cada operação diz o escopo que o token precisa (`x-token-scope`; `*` é qualquer token). Erros em `application/problem+json` (RFC 9457) com um `code` estável. Limite de 120 requisições por minuto por token (balde de 60), informado nos cabeçalhos `RateLimit-Policy` e `RateLimit` (draft-ietf-httpapi-ratelimit-headers); acima dele, 429 com `Retry-After`. Os POST que criam ou cobram aceitam `Idempotency-Key`. Listas com paginação por cursor (`limit`, `cursor`, `next_cursor`) e operações assíncronas acompanhadas em `GET /cloud/operations/{id}`. Guia: https://docs.halver.com.br/api. Halver API to manage the bare metal servers, SSH keys, private networks, elastic IPs, invoices and support tickets of your account. Authenticate with an API token (`Authorization: Bearer sc_live_…`) created under Developers › API tokens in the panel. Each operation names the scope the token needs (`x-token-scope`). Errors are `application/problem+json` (RFC 9457) with a stable `code`; rate limit: 120 requests per minute per token (bucket of 60), reported in `RateLimit-Policy`/`RateLimit`; POSTs that create or charge accept `Idempotency-Key`; lists page with cursors; asynchronous operations are polled at `GET /cloud/operations/{id}`. title: Halver API version: 1.0.0 openapi: 3.0.3 paths: /cloud/catalog: get: description: |- Escopo do token / token scope: `servers:read`. Locais, planos com estoque por local, sistemas, se a conta está verificada (cartão salvo) e se o provisionamento está disponível. operationId: cloudCatalog responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudCatalog' description: Catálogo. "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: O que dá para contratar agora tags: - cloud x-token-scope: servers:read /cloud/coupons/check: post: description: |- Escopo do token / token scope: `servers:write`. Não resgata: mostra a primeira fatura do plano (mês cheio) com o cupom. Cupom que não vale, por qualquer motivo (inexistente, vencido, esgotado, já usado, de outro plano), responde 422 `coupon.unavailable` com a mesma mensagem ("Cupom inválido ou indisponível.", campo coupon_code): respostas diferentes diriam quais códigos existem. Limite de tentativas por conta e por IP (429 `coupon.rate_limited`). O cartão continua obrigatório. operationId: cloudCheckCoupon requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudCouponCheckInput' required: true responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudCouponQuote' description: Cupom válido. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "422": $ref: '#/components/responses/UnprocessableEntity' "429": $ref: '#/components/responses/TooManyRequests' security: - apiToken: [] summary: Confere um cupom no checkout do deploy tags: - cloud x-token-scope: servers:write /cloud/network: get: description: |- Escopo do token / token scope: `networks:read`. Regiões onde a conta tem servidores (e as outras com datacenter), com o que cada uma oferece agora (IP elástico e preço, firewall, rede privada), as cotas e se a conta tem cartão salvo. Um recurso só é oferecido onde tem efeito: IPs elásticos e firewall pedem `routed_by_gateway` na região (o OPNsense é o gateway dos servidores) e um gateway cadastrado; redes privadas pedem `switch_automated` (a plataforma configura as portas do switch). IPs elásticos e redes privadas também pedem um bloco com espaço. operationId: cloudNetworkOverview responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudNetworkOverview' description: Visão da rede. "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: Visão da rede da conta tags: - cloud x-token-scope: networks:read /cloud/network/elastic-ips: get: description: 'Escopo do token / token scope: `ips:read`.' operationId: cloudListElasticIPs parameters: - $ref: '#/components/parameters/PageLimit' - $ref: '#/components/parameters/PageCursor' responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudElasticIPList' description: IPs elásticos vivos. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: IPs elásticos tags: - cloud x-token-scope: ips:read post: description: |- Escopo do token / token scope: `ips:write`. O próximo endereço livre dos blocos de IP flutuante da região, opcionalmente já num servidor da mesma região. Com preço, é cobrado no ato como um servidor: contrato com o produto do bloco e a primeira fatura no cartão salvo; recusa libera o endereço e nada é cobrado (422 `cloud.payment_refused`). Sem resposta da operadora fica `pending_payment`. O job `cloud-network` escreve a rota no gateway (`configuring` → `active`). Limite de 10 por conta (422 `cloud.quota_exceeded`). Numa região sem `routed_by_gateway` (ou sem gateway) é recusado antes de qualquer cobrança (409 `cloud.elastic_ips_unavailable`). Aceita `Idempotency-Key`: repetir a mesma chave nunca cobra de novo. operationId: cloudCreateElasticIP parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudElasticIPInput' required: true responses: "201": content: application/json: schema: $ref: '#/components/schemas/CloudElasticIP' description: IP elástico. headers: Idempotent-Replayed: $ref: '#/components/headers/IdempotentReplayed' Retry-After: $ref: '#/components/headers/PollAfter' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Cria um IP elástico tags: - cloud x-token-scope: ips:write /cloud/network/elastic-ips/{id}: delete: description: |- Escopo do token / token scope: `ips:write`. A rota sai do gateway (`releasing`) e o endereço volta ao bloco. O contrato termina no fim do período já pago, sem reembolso. operationId: cloudReleaseElasticIP responses: "202": content: application/json: schema: $ref: '#/components/schemas/CloudElasticIP' description: Liberando. headers: Retry-After: $ref: '#/components/headers/PollAfter' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' security: - apiToken: [] summary: Libera o IP elástico tags: - cloud x-token-scope: ips:write get: description: 'Escopo do token / token scope: `ips:read`.' operationId: cloudGetElasticIP responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudElasticIP' description: IP elástico. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Detalha um IP elástico tags: - cloud x-token-scope: ips:read parameters: - $ref: '#/components/parameters/Id' /cloud/network/elastic-ips/{id}/assign: parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `ips:write`. `server_id` é um servidor da mesma região (422 `cloud.other_region` fora dela) ou `null` (sem servidor). Com o IP já roteado para outro servidor o status é `moving`; o job move a rota. Apontar para um servidor numa região sem `routed_by_gateway` é recusado (409 `cloud.elastic_ips_unavailable`); tirar do servidor vale em qualquer região. Aceita `Idempotency-Key`. operationId: cloudAssignElasticIP parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudAssignInput' required: true responses: "202": content: application/json: schema: $ref: '#/components/schemas/CloudElasticIP' description: Mudança em andamento. headers: Idempotent-Replayed: $ref: '#/components/headers/IdempotentReplayed' Retry-After: $ref: '#/components/headers/PollAfter' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' security: - apiToken: [] summary: Atribui ou move o IP elástico tags: - cloud x-token-scope: ips:write /cloud/network/private-networks: get: description: 'Escopo do token / token scope: `networks:read`.' operationId: cloudListPrivateNetworks parameters: - $ref: '#/components/parameters/PageLimit' - $ref: '#/components/parameters/PageCursor' responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudPrivateNetworkList' description: Redes privadas vivas. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: Redes privadas tags: - cloud x-token-scope: networks:read post: description: |- Escopo do token / token scope: `networks:write`. Uma VLAN da região (de 1000 a 3999, sem repetir VLAN de bloco ou de outra rede da região) e uma sub-rede /24 do bloco de rede privada da região. O job define a VLAN no OPNsense (`configuring` → `active`). O cliente configura a VLAN (802.1Q) no sistema; o tráfego é livre e não é filtrado. Limite de 5 por conta. Numa região sem `switch_automated` é recusado (409 `cloud.private_networks_unavailable`). Aceita `Idempotency-Key`. operationId: cloudCreatePrivateNetwork parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudPrivateNetworkInput' required: true responses: "201": content: application/json: schema: $ref: '#/components/schemas/CloudPrivateNetwork' description: Rede privada. headers: Idempotent-Replayed: $ref: '#/components/headers/IdempotentReplayed' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' security: - apiToken: [] summary: Cria uma rede privada tags: - cloud x-token-scope: networks:write /cloud/network/private-networks/{id}: delete: description: |- Escopo do token / token scope: `networks:write`. Só sem servidores (409 `cloud.private_network_in_use`); a VLAN sai do gateway e a VLAN e a sub-rede ficam livres. operationId: cloudDeletePrivateNetwork responses: "204": description: Apagando. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' security: - apiToken: [] summary: Apaga a rede privada tags: - cloud x-token-scope: networks:write get: description: 'Escopo do token / token scope: `networks:read`.' operationId: cloudGetPrivateNetwork responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudPrivateNetwork' description: Rede privada. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Detalha uma rede privada tags: - cloud x-token-scope: networks:read parameters: - $ref: '#/components/parameters/Id' put: description: 'Escopo do token / token scope: `networks:write`.' operationId: cloudUpdatePrivateNetwork requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudPrivateNetworkUpdate' required: true responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudPrivateNetwork' description: Rede privada. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' security: - apiToken: [] summary: Altera a descrição tags: - cloud x-token-scope: networks:write /cloud/network/private-networks/{id}/servers: parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `networks:write`. Só servidores da mesma região (422 `cloud.other_region`), numa região com `switch_automated` (409 `cloud.private_networks_unavailable` fora dela). O servidor recebe um endereço sugerido da sub-rede e o job `cloud-network` leva a VLAN à porta do switch dele (`connecting` → `active` ou `failed`, com a resposta do switch em `apply_error`). Colocar de novo um servidor `failed` tenta outra vez. Aceita `Idempotency-Key`. operationId: cloudJoinPrivateNetwork parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudServerRef' required: true responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudPrivateNetwork' description: Rede privada. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' security: - apiToken: [] summary: Coloca um servidor na rede tags: - cloud x-token-scope: networks:write /cloud/network/private-networks/{id}/servers/{serverID}: delete: description: |- Escopo do token / token scope: `networks:write`. Fica `removing` até o job tirar a VLAN da porta do switch e então sai. Numa região sem `switch_automated` sai na hora (a plataforma não gravou nada no switch). operationId: cloudLeavePrivateNetwork responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudPrivateNetwork' description: Rede privada. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Tira um servidor da rede tags: - cloud x-token-scope: networks:write parameters: - $ref: '#/components/parameters/Id' - $ref: '#/components/parameters/ServerId' /cloud/operations: get: description: |- Escopo do token / token scope: `servers:read`. Deploys, reinstalações, apagamentos e remoções, na ordem em que foram pedidos; `server_id` filtra um servidor, `external_id` e `end_customer_ref` os servidores com essas referências (as atuais de cada servidor). As operações de um servidor removido continuam legíveis. Paginação por cursor opcional. operationId: cloudListOperations parameters: - $ref: '#/components/parameters/PageLimit' - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/ExternalIdQuery' - $ref: '#/components/parameters/EndCustomerRefQuery' - in: query name: server_id required: false schema: format: uuid type: string responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudOperationList' description: Operações. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: Operações dos servidores da conta tags: - cloud x-token-scope: servers:read /cloud/operations/{id}: get: description: |- Escopo do token / token scope: `servers:read`. O contrato de espera: consulte até `status` ser `succeeded` ou `failed`, respeitando `Retry-After` enquanto `pending` ou `running`. operationId: cloudGetOperation responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudOperation' description: Operação. headers: Retry-After: $ref: '#/components/headers/PollAfter' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Detalha uma operação tags: - cloud x-token-scope: servers:read parameters: - $ref: '#/components/parameters/Id' /cloud/servers: get: description: |- Escopo do token / token scope: `servers:read`. Filtros: `status` (um ou mais, separados por vírgula), `site_id`, `plan_id`, `hostname_prefix`, `tag`, `external_id` e `end_customer_ref`, aplicados no banco. Paginação por cursor opcional (`limit`, `cursor`). operationId: cloudListServers parameters: - $ref: '#/components/parameters/PageLimit' - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/ExternalIdQuery' - $ref: '#/components/parameters/EndCustomerRefQuery' - description: pending_payment, deploying, active, failed, wiping, empty ou deleting; vários separados por vírgula. in: query name: status required: false schema: type: string - in: query name: site_id required: false schema: format: uuid type: string - in: query name: plan_id required: false schema: format: uuid type: string - in: query name: hostname_prefix required: false schema: maxLength: 63 type: string - in: query name: tag required: false schema: maxLength: 64 type: string responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudServerList' description: Servidores. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: Servidores da conta tags: - cloud x-token-scope: servers:read post: description: |- Escopo do token / token scope: `servers:write`. Exige cartão salvo, menos para conta de uso interno. Reserva uma máquina em estoque, abre o contrato mensal do servidor (com o cupom, se houver) e registra a operação, que segue em segundo plano (acompanhe `GET /cloud/operations/{operation_id}`). Primeira fatura sem nada a pagar (conta interna, cupom de 100%) não passa pelo cartão. Cupom que não vale responde 422 `coupon.unavailable` ("Cupom inválido ou indisponível.", sem o motivo) e desfaz a compra; com cupom, o pedido conta no limite de tentativas de cupom (429 `coupon.rate_limited`). Aceita `Idempotency-Key`: repetir a mesma chave nunca cobra nem cria um segundo servidor. `external_id` (o pedido do revendedor) é único na conta entre os servidores não removidos: um segundo deploy com o mesmo valor responde 409 `cloud.external_id_taken` com o id do servidor que já o tem em `existing_id`, sem reservar máquina nem cobrar; a mesma `Idempotency-Key` continua devolvendo a resposta guardada do primeiro pedido. operationId: cloudDeployServer parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudDeployInput' required: true responses: "201": content: application/json: schema: $ref: '#/components/schemas/CloudServer' description: Servidor em deploy. headers: Idempotent-Replayed: $ref: '#/components/headers/IdempotentReplayed' Retry-After: $ref: '#/components/headers/PollAfter' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' "429": $ref: '#/components/responses/TooManyRequests' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Faz o deploy de um servidor tags: - cloud x-token-scope: servers:write /cloud/servers/{id}: delete: description: |- Escopo do token / token scope: `servers:delete`. `confirm` deve ser o hostname. O cancelamento vale no fim do período já pago (`cancel_at`, o último dia): não há nova fatura nem reembolso, o cliente segue usando a máquina e pode desfazer em `DELETE /cloud/servers/{id}/cancellation`. Depois desse dia o contrato termina, os discos são apagados e a máquina volta ao estoque. Para só apagar o sistema e manter a máquina, use `POST /cloud/servers/{id}/wipe`. `release=immediate` (para automação que precisa da máquina livre na hora) remove o servidor **agora**: o contrato termina hoje, os discos são apagados e a máquina volta ao estoque, **sem reembolso** do período já pago (as faturas de períodos ainda não iniciados são canceladas). Fatura em aberto do período em uso recusa (422 `cloud.unpaid_invoices`): a liberação não perdoa dívida. Desligado por padrão: 422 `cloud.immediate_release_disabled` quando a plataforma não a libera. Acompanhe a remoção por `GET /cloud/operations/{operation_id}`; ao terminar, `GET /cloud/servers/{id}` responde 404. operationId: cloudDeleteServer parameters: - description: '`end_of_period` (padrão) ou `immediate` (sem reembolso).' in: query name: release required: false schema: enum: - end_of_period - immediate type: string requestBody: content: application/json: schema: additionalProperties: false properties: confirm: type: string required: - confirm type: object required: true responses: "202": content: application/json: schema: $ref: '#/components/schemas/CloudServer' description: Cancelamento agendado (padrão) ou remoção em andamento (`release=immediate`, status `deleting`). headers: Retry-After: $ref: '#/components/headers/PollAfter' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Cancela a assinatura do servidor tags: - cloud x-token-scope: servers:delete get: description: 'Escopo do token / token scope: `servers:read`.' operationId: cloudGetServer responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudServer' description: Servidor com as etapas da operação atual. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Detalha um servidor tags: - cloud x-token-scope: servers:read parameters: - $ref: '#/components/parameters/Id' patch: description: |- Escopo do token / token scope: `servers:write`. Só rótulos, em qualquer status menos removido, sem tocar na máquina: o hostname muda na plataforma e o sistema mantém o da instalação até a próxima reinstalação. Tags filtram a lista (`tag`). `external_id` que outro servidor da conta já tem responde 409 `cloud.external_id_taken` com o id dele em `existing_id`. operationId: cloudUpdateServer requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudServerUpdate' required: true responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudServer' description: Servidor. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' security: - apiToken: [] summary: Altera hostname, tags, descrição e as referências do revendedor tags: - cloud x-token-scope: servers:write /cloud/servers/{id}/boot-device: parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `servers:write`. Só o próximo boot: `disk` os discos ou `pxe` a rede de instalação (num servidor com sistema ela volta aos discos). O Setup do BIOS é protegido por senha e não é oferecido; uma mudança no BIOS se pede por chamado. Não reinicia. Com o servidor ativo, ou vazio com "Sem sistema"; 409 nos demais estados. Mídia virtual não é oferecida (licença da IPMI). Pelo servidor MCP só depois da aprovação da pessoa no portal. operationId: cloudSetBootDevice requestBody: content: application/json: schema: additionalProperties: false properties: device: enum: - pxe - disk type: string required: - device type: object required: true responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudServer' description: Servidor. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Dispositivo do próximo boot tags: - cloud x-token-scope: servers:write /cloud/servers/{id}/cancellation: delete: description: |- Escopo do token / token scope: `servers:write`. A assinatura segue e volta a ser cobrada no próximo período. operationId: cloudUndoCancelServer responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudServer' description: Assinatura mantida. "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' security: - apiToken: [] summary: Desfaz o cancelamento agendado tags: - cloud x-token-scope: servers:write parameters: - $ref: '#/components/parameters/Id' /cloud/servers/{id}/console: get: description: |- Escopo do token / token scope: `servers:read`. Se o servidor pode abrir uma sessão do console serial (IPMI SOL; `unavailable` diz por que não), o tempo sem uso e o máximo de uma sessão, as teclas nomeadas, a cota da conta, a sessão viva (`live`) e as 10 últimas encerradas. operationId: cloudConsole responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudConsoleOverview' description: Console serial do servidor. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Console serial do servidor tags: - cloud x-token-scope: servers:read parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `servers:console`. Acesso de teclado ao servidor pelo SOL da IPMI, aberto pelo broker do motor (as credenciais da IPMI nunca saem do motor). Com o servidor em repouso (ativo, sem sistema ou falho) e fora de suspensão (422 `cloud.console_unavailable`); uma sessão viva por servidor e por máquina (409 `cloud.console_open`) e 5 por conta (422 `cloud.quota_exceeded`). Fecha com 15 minutos sem uso (ler a tela ou digitar), em 2 horas, quando o cliente ou a equipe encerra e antes de qualquer operação nossa no servidor. Pelo servidor MCP só depois da aprovação da pessoa no portal (403 sem ela). 503 `cloud.console_failed` quando o SOL não abre. operationId: cloudOpenConsole requestBody: content: application/json: schema: additionalProperties: false type: object required: false responses: "201": content: application/json: schema: $ref: '#/components/schemas/CloudConsoleSession' description: Sessão aberta. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Abre o console serial tags: - cloud x-token-scope: servers:console /cloud/servers/{id}/console/{sessionID}: delete: description: |- Escopo do token / token scope: `servers:console`. O acesso termina na hora; a sessão do broker fecha e a gravação é guardada (`closed`), ou o job `cloud-console` termina isso (`closing`). Repetir numa sessão encerrada devolve a sessão como está. operationId: cloudEndConsole responses: "202": content: application/json: schema: $ref: '#/components/schemas/CloudConsoleSession' description: Encerrando ou encerrada. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Encerra o console serial tags: - cloud x-token-scope: servers:console get: description: 'Escopo do token / token scope: `servers:read`.' operationId: cloudGetConsole responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudConsoleSession' description: Sessão. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Detalha uma sessão do console serial tags: - cloud x-token-scope: servers:read parameters: - $ref: '#/components/parameters/Id' - in: path name: sessionID required: true schema: format: uuid type: string /cloud/servers/{id}/console/{sessionID}/input: parameters: - $ref: '#/components/parameters/Id' - in: path name: sessionID required: true schema: format: uuid type: string post: description: |- Escopo do token / token scope: `servers:console`. Textos e teclas nomeadas, na ordem (até 64 itens e 4096 bytes). Não há Ctrl-Alt-Del nem break serial: reset e energia nunca passam pelo console. Toda entrada é registrada com quem digitou (usuário, token de API ou cliente OAuth pelo MCP; o texto guardado cifrado). Com `wait_for`, espera esse texto na tela por até `timeout_seconds` (máx. 25) e diz em `matched` se apareceu. 429 `cloud.console_rate_limited` acima de 5 envios por segundo (rajada de 30). operationId: cloudConsoleInput requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudConsoleInput' required: true responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudConsoleScreen' description: A tela depois da entrada. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "429": $ref: '#/components/responses/TooManyRequests' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Digita no console serial tags: - cloud x-token-scope: servers:console /cloud/servers/{id}/console/{sessionID}/output: get: description: |- Escopo do token / token scope: `servers:console`. Os bytes que o servidor mandou a partir de `offset` (até 64 KiB, em base64), para um emulador de terminal no navegador. `reset` diz que o pedido estava fora do que o broker guarda (1 MiB) e a resposta começa no que ainda há. Conta como uso da sessão. operationId: cloudConsoleOutput parameters: - in: query name: offset required: false schema: minimum: 0 type: integer responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudConsoleOutput' description: Saída. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Saída bruta do console serial tags: - cloud x-token-scope: servers:console parameters: - $ref: '#/components/parameters/Id' - in: path name: sessionID required: true schema: format: uuid type: string /cloud/servers/{id}/console/{sessionID}/screen: get: description: |- Escopo do token / token scope: `servers:console`. A tela que o emulador de terminal do broker mostra (linhas de texto e cursor). Conta como uso da sessão. É saída do servidor: dado não confiável. 409 `cloud.console_closed` com a sessão fora do ar. operationId: cloudConsoleScreen responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudConsoleScreen' description: Tela. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Tela do console serial tags: - cloud x-token-scope: servers:console parameters: - $ref: '#/components/parameters/Id' - in: path name: sessionID required: true schema: format: uuid type: string /cloud/servers/{id}/console/{sessionID}/transcript: get: description: |- Escopo do token / token scope: `servers:console`. A gravação da sessão encerrada (asciicast v2: saída e entrada com o tempo de cada uma), como arquivo; toca no asciinema. 404 enquanto não foi guardada ou quando o broker não a tinha. operationId: cloudConsoleTranscript responses: "200": content: application/x-asciicast: schema: type: string description: Gravação (asciicast v2). "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Gravação da sessão do console serial tags: - cloud x-token-scope: servers:console parameters: - $ref: '#/components/parameters/Id' - in: path name: sessionID required: true schema: format: uuid type: string /cloud/servers/{id}/ipmi-vpn: get: description: |- Escopo do token / token scope: `servers:read`. Se o servidor pode abrir uma sessão de VPN até a IPMI (`unavailable` diz por que não), as durações, a cota da conta, as portas liberadas e as sessões do servidor (as vivas e as 5 últimas encerradas). A senha da conta da IPMI vem enquanto a sessão está viva. operationId: cloudIPMIVPN responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudIPMIVPNOverview' description: IPMI do servidor. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: IPMI do servidor (VPN) tags: - cloud x-token-scope: servers:read parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `servers:console`. Com o servidor em repouso (ativo, sem sistema ou falho) e na região com VPN (422 `cloud.vpn_unavailable`); indisponível enquanto uma operação nossa roda (deploy, reinstalação, limpeza, exclusão) e enquanto o servidor está desligado por falta de pagamento (409 `cloud.server_suspended`; o desligamento encerra as sessões abertas antes de desligar). `public_key` é a chave pública WireGuard gerada no dispositivo do cliente: a chave privada nunca chega à plataforma. A sessão nasce `opening` com o endereço do túnel e a conta da IPMI (usuário e senha gerados, papel Operator); o job `cloud-ipmi-vpn` cria a conta na IPMI e o peer no OPNsense (`active`, com a chave pública do OPNsense e o arquivo do WireGuard) e tira tudo ao vencer. Uma sessão viva por servidor (409 `cloud.vpn_session_open`) e 5 por conta (422 `cloud.quota_exceeded`). Por token de API só com o escopo `servers:console`, e pelo servidor MCP só depois da aprovação da pessoa no portal; a senha da IPMI nunca volta ao MCP (a pessoa a lê no portal). operationId: cloudOpenIPMIVPN requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudIPMIVPNInput' required: true responses: "201": content: application/json: schema: $ref: '#/components/schemas/CloudIPMIVPNSession' description: Sessão abrindo. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' security: - apiToken: [] summary: Abre uma sessão de VPN até a IPMI tags: - cloud x-token-scope: servers:console /cloud/servers/{id}/ipmi-vpn/{sessionID}: delete: description: |- Escopo do token / token scope: `servers:console`. A sessão passa a `closing` (motivo `ended`); o job tira as regras e o peer do OPNsense e depois a conta da IPMI (`closed`). Repetir numa sessão já encerrada devolve a sessão como está. operationId: cloudEndIPMIVPN responses: "202": content: application/json: schema: $ref: '#/components/schemas/CloudIPMIVPNSession' description: Encerrando. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Encerra a sessão antes do fim tags: - cloud x-token-scope: servers:console get: description: 'Escopo do token / token scope: `servers:read`.' operationId: cloudGetIPMIVPN responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudIPMIVPNSession' description: Sessão. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Detalha uma sessão de VPN até a IPMI tags: - cloud x-token-scope: servers:read parameters: - $ref: '#/components/parameters/Id' - in: path name: sessionID required: true schema: format: uuid type: string /cloud/servers/{id}/power: parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `servers:write`. Com o servidor ativo, ou vazio com "Sem sistema" (o sistema próprio do cliente, instalado pela IPMI); 409 nos demais estados. Aceita `Idempotency-Key`. operationId: cloudPowerServer parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: content: application/json: schema: additionalProperties: false properties: action: enum: - "on" - "off" - reboot type: string required: - action type: object required: true responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudServer' description: Servidor. headers: Idempotent-Replayed: $ref: '#/components/headers/IdempotentReplayed' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Liga, desliga ou reinicia tags: - cloud x-token-scope: servers:write /cloud/servers/{id}/reinstall: parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `servers:delete`. Apaga os discos e instala de novo; sistema, chaves e user data opcionais. `user_data` ausente mantém o do servidor (roda de novo), vazio remove e um documento novo substitui. Aceita `Idempotency-Key`. operationId: cloudReinstallServer parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: content: application/json: schema: additionalProperties: false properties: disk_layout: description: 'Legado (use storage, que tem precedência): single é o disco padrão (ext4 no Proxmox VE) e raid1 o espelho do primeiro par de discos iguais (ZFS RAID 1 no Proxmox VE). Sem storage nem disk_layout, a reinstalação mantém o armazenamento da última quando o novo sistema o aceita como está, senão usa o padrão do sistema.' enum: - single - raid1 - null nullable: true type: string os_image_id: format: uuid nullable: true type: string password: description: Gera uma nova senha (enviada por e-mail); ausente mantém a escolha da última instalação. nullable: true type: boolean ssh_key_ids: items: format: uuid type: string nullable: true type: array storage: $ref: '#/components/schemas/CloudStorageInput' user_data: description: Ausente ou nulo mantém o user data do servidor; vazio remove; um documento substitui (regras do deploy). maxLength: 90000 nullable: true type: string type: object required: true responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudServer' description: Servidor em reinstalação. headers: Idempotent-Replayed: $ref: '#/components/headers/IdempotentReplayed' Retry-After: $ref: '#/components/headers/PollAfter' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Reinstala um servidor tags: - cloud x-token-scope: servers:delete /cloud/servers/{id}/wipe: parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `servers:delete`. `confirm` deve ser o hostname. Os discos são apagados e o servidor fica `empty` (sem sistema): a máquina continua reservada para o cliente e a assinatura segue. Um novo deploy usa `POST /cloud/servers/{id}/reinstall`. Aceita `Idempotency-Key`. operationId: cloudWipeServer parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: content: application/json: schema: additionalProperties: false properties: confirm: type: string required: - confirm type: object required: true responses: "202": content: application/json: schema: $ref: '#/components/schemas/CloudServer' description: Apagando o sistema. headers: Idempotent-Replayed: $ref: '#/components/headers/IdempotentReplayed' Retry-After: $ref: '#/components/headers/PollAfter' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' "503": $ref: '#/components/responses/ServiceUnavailable' security: - apiToken: [] summary: Apaga o sistema e os dados, mantendo a máquina tags: - cloud x-token-scope: servers:delete /cloud/ssh-keys: get: description: 'Escopo do token / token scope: `ssh_keys:read`.' operationId: cloudListSSHKeys parameters: - $ref: '#/components/parameters/PageLimit' - $ref: '#/components/parameters/PageCursor' responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudSSHKeyList' description: Chaves. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: Chaves SSH da conta tags: - cloud x-token-scope: ssh_keys:read post: description: |- Escopo do token / token scope: `ssh_keys:write`. ed25519, ECDSA ou RSA de 2048 bits ou mais; uma linha de authorized_keys sem opções. Aceita `Idempotency-Key`. operationId: cloudAddSSHKey parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudSSHKeyInput' required: true responses: "201": content: application/json: schema: $ref: '#/components/schemas/CloudSSHKey' description: Chave criada. headers: Idempotent-Replayed: $ref: '#/components/headers/IdempotentReplayed' "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "409": $ref: '#/components/responses/Conflict' "422": $ref: '#/components/responses/UnprocessableEntity' security: - apiToken: [] summary: Adiciona uma chave SSH tags: - cloud x-token-scope: ssh_keys:write /cloud/ssh-keys/{id}: delete: description: |- Escopo do token / token scope: `ssh_keys:write`. Servidores já instalados mantêm a chave até um reinstall. operationId: cloudDeleteSSHKey responses: "204": description: Removida. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Remove uma chave SSH tags: - cloud x-token-scope: ssh_keys:write get: description: 'Escopo do token / token scope: `ssh_keys:read`.' operationId: cloudGetSSHKey responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudSSHKey' description: Chave. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Detalha uma chave SSH tags: - cloud x-token-scope: ssh_keys:read parameters: - $ref: '#/components/parameters/Id' patch: description: |- Escopo do token / token scope: `ssh_keys:write`. Só o nome muda; a chave em si nunca (cadastre outra). operationId: cloudRenameSSHKey requestBody: content: application/json: schema: $ref: '#/components/schemas/CloudSSHKeyUpdate' required: true responses: "200": content: application/json: schema: $ref: '#/components/schemas/CloudSSHKey' description: Chave. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Renomeia uma chave SSH tags: - cloud x-token-scope: ssh_keys:write /me: get: description: 'Escopo do token / token scope: qualquer token / any token.' operationId: getMe responses: "200": content: application/json: schema: $ref: '#/components/schemas/Me' description: Usuário autenticado. "401": $ref: '#/components/responses/Unauthorized' security: - apiToken: [] summary: Usuário atual e permissões tags: - me x-token-scope: '*' /portal/invoices: get: description: |- Escopo do token / token scope: `invoices:read`. Visão do cliente (sem dados internos). As cobranças não são incluídas. `external_id` e `end_customer_ref` trazem as faturas dos contratos dos servidores com essa referência (a atual de cada servidor, removidos inclusive). operationId: portalListInvoices parameters: - in: query name: status required: false schema: $ref: '#/components/schemas/InvoiceStatus' - $ref: '#/components/parameters/Search' - $ref: '#/components/parameters/ContractIdQuery' - $ref: '#/components/parameters/ExternalIdQuery' - $ref: '#/components/parameters/EndCustomerRefQuery' - $ref: '#/components/parameters/DueFrom' - $ref: '#/components/parameters/DueTo' - $ref: '#/components/parameters/Competence' - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' responses: "200": content: application/json: schema: $ref: '#/components/schemas/PortalInvoicePage' description: Página de faturas. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: Lista as faturas do cliente tags: - portal x-token-scope: invoices:read /portal/invoices/{id}: get: description: |- Escopo do token / token scope: `invoices:read`. Visão do cliente; cobranças canceladas ou recusadas não são listadas. operationId: portalGetInvoice responses: "200": content: application/json: schema: $ref: '#/components/schemas/PortalInvoice' description: Fatura. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Detalha uma fatura do cliente com cobranças tags: - portal x-token-scope: invoices:read parameters: - $ref: '#/components/parameters/Id' /portal/invoices/{id}/receipt: get: description: |- Escopo do token / token scope: `invoices:read`. Emite o comprovante no primeiro acesso. Fatura não paga: 422 `invoice.not_paid`. operationId: portalGetReceipt responses: "200": content: application/json: schema: $ref: '#/components/schemas/Receipt' description: Comprovante. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "422": $ref: '#/components/responses/UnprocessableEntity' security: - apiToken: [] summary: Comprovante de pagamento de uma fatura do cliente tags: - portal x-token-scope: invoices:read parameters: - $ref: '#/components/parameters/Id' /portal/payment-methods: get: description: 'Escopo do token / token scope: `payment_methods:read`.' operationId: portalListPaymentMethods responses: "200": content: application/json: schema: $ref: '#/components/schemas/PaymentMethodList' description: Cartões salvos (padrão primeiro). "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: Lista os cartões salvos tags: - portal x-token-scope: payment_methods:read /portal/support/tickets: get: description: |- Escopo do token / token scope: `support:read`. Chamados da conta do usuário, da conversa mais recente para a mais antiga, opcionalmente de uma ou mais situações. Sem `limit` nem `offset` vêm os 200 mais recentes; `meta.total` é o total da situação pedida e `counts` conta os chamados em andamento e resolvidos da conta. `unread` diz que há resposta da equipe ainda não lida; `expected` é quando a equipe deve responder. Notas internas, nomes da equipe, grupo, etiquetas e o responsável nunca aparecem no portal. operationId: portalListTickets parameters: - description: Uma ou mais situações (`open`, `answered`, `resolved`) separadas por vírgula; `open,answered` são os em andamento. in: query name: status schema: example: open,answered type: string - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' responses: "200": content: application/json: schema: $ref: '#/components/schemas/PortalTicketList' description: Chamados. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' security: - apiToken: [] summary: Lista os chamados da conta tags: - support x-token-scope: support:read post: description: |- Escopo do token / token scope: `support:write`. Abre o chamado com a primeira mensagem; `server_id` é um servidor da própria conta (outro servidor: 400 no campo). A equipe recebe a notificação no sino e, se configurado, um aviso por e-mail sem o conteúdo. Rate limit por conta: 10 por dia (burst 5). operationId: portalOpenTicket requestBody: content: application/json: schema: $ref: '#/components/schemas/TicketOpenInput' required: true responses: "201": content: application/json: schema: $ref: '#/components/schemas/PortalTicket' description: Chamado aberto, com a conversa. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "429": $ref: '#/components/responses/TooManyRequests' security: - apiToken: [] summary: Abre um chamado tags: - support x-token-scope: support:write /portal/support/tickets/{id}: get: description: |- Escopo do token / token scope: `support:read`. Abrir o chamado marca as respostas da equipe como lidas. Chamado de outra conta: 404. Depois do prazo de guarda (1 ano após a resolução) a conversa vem vazia e `purged` é verdadeiro. operationId: portalGetTicket responses: "200": content: application/json: schema: $ref: '#/components/schemas/PortalTicket' description: Chamado. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Mostra um chamado com a conversa tags: - support x-token-scope: support:read parameters: - $ref: '#/components/parameters/Id' /portal/support/tickets/{id}/messages: parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `support:write`. O chamado volta a esperar a equipe; o resolvido reabre. Num chamado fechado (resolvido há mais dias que `close_days`) a mensagem abre um chamado novo de continuação, que é o devolvido (outro `id`, com `follow_up_of`). Chamado juntado a outro: 409 `support.ticket_merged`. `internal` e `macro_id` só valem para a equipe (400). `attachment_ids`: anexos enviados antes por `/portal/support/attachments`. Conversa apagada pelo prazo de guarda ou com 200 mensagens: 409. Rate limit por conta: 60 por hora (burst 20). operationId: portalReplyTicket requestBody: content: application/json: schema: $ref: '#/components/schemas/TicketMessageInput' required: true responses: "201": content: application/json: schema: $ref: '#/components/schemas/PortalTicket' description: Mensagem gravada; o chamado com a conversa. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' "409": $ref: '#/components/responses/Conflict' "429": $ref: '#/components/responses/TooManyRequests' security: - apiToken: [] summary: Responde no chamado tags: - support x-token-scope: support:write /portal/support/tickets/{id}/resolve: parameters: - $ref: '#/components/parameters/Id' post: description: |- Escopo do token / token scope: `support:write`. De novo não muda nada; uma mensagem nova reabre. operationId: portalResolveTicket responses: "200": content: application/json: schema: $ref: '#/components/schemas/PortalTicket' description: Chamado. "400": $ref: '#/components/responses/BadRequest' "401": $ref: '#/components/responses/Unauthorized' "403": $ref: '#/components/responses/Forbidden' "404": $ref: '#/components/responses/NotFound' security: - apiToken: [] summary: Marca o chamado como resolvido tags: - support x-token-scope: support:write security: - apiToken: [] servers: - url: https://dash.halver.com.br/api/v1 tags: - description: Portal do cliente final (servidores bare metal, chaves SSH e rede - IPs elásticos, redes privadas e firewalls), restrito à conta do usuário autenticado. name: cloud - description: Usuário autenticado. name: me - description: Portal do cliente (sempre restrito ao cliente do usuário autenticado). name: portal - description: | Chamados de suporte: o cliente abre e conversa na área do cliente ("Suporte"); a equipe responde no admin ("Atendimento"), com notas internas, responsável e o contexto da conta. name: support