{
  "openapi": "3.1.0",
  "info": {
    "title": "API do Prodio",
    "version": "1.0.0",
    "summary": "Leia e grave os dados da sua fábrica a partir do sistema da sua empresa.",
    "description": "A API do Prodio deixa o sistema da sua empresa (ERP, loja, planilha automatizada, BI) ler e gravar os mesmos dados que\nvocê vê nas telas. O endereço é `https://api.prodio.com.br/v1`, as respostas são JSON UTF-8 em português e o acesso é\npor um **token da empresa**, criado em Configurações › API por um administrador.\n\n**Versão.** A versão `1.0.0` deste documento acompanha a rota `/v1`. Mudança **aditiva** (campo novo, rota nova,\nvalor novo num enum) não muda a versão da API: ignore os campos que não conhece. Quebra de compatibilidade vira `/v2`.\n\n**Autenticação.** Cabeçalho `Authorization: Bearer prodio_live_…` (token de 55 caracteres). Token na URL é recusado\ncom 400 `token_na_url`. Cada rota exige o escopo indicado em `x-prodio-escopo` (`qualquer` = qualquer token);\n`:escrever` inclui `:ler` do mesmo recurso. `custos:ler` libera os campos em reais; sem ele eles vêm `null` e o resto\nda resposta continua igual.\n\n**Limites.** 120 chamadas por minuto por token e 300 por minuto por empresa, em janela fixa de 1 minuto. Acima disso\n429 `limite_excedido` com `Retry-After` (segundos). Toda resposta autenticada traz `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` e `X-RateLimit-Reset` (instante Unix em segundos). Toda resposta traz `X-Request-Id`: cite-o\nao pedir suporte.\n\n**Paginação e leitura incremental.** Toda lista devolve `{ \"dados\": [ … ], \"proximo_cursor\": \"…\" }`. `limite` padrão\n50, máximo 200; ordem estável crescente por `atualizado_em` (ou `criado_em` nos movimentos) com desempate por `id`.\nRepita exatamente o `proximo_cursor` recebido no parâmetro `cursor`; `null` = acabou; cursor adulterado → 400\n`cursor_invalido`. `atualizado_desde` e `desde` são **inclusivos** (a última linha da carga anterior volta: trate-a\ncomo atualização). Há uma **janela de estabilização de 30 segundos**: a lista só devolve linhas alteradas há mais de\nmeio minuto, para uma gravação longa nunca ficar para trás. `GET …/{id}` não tem janela.\n\n**Datas e instantes.** Datas `AAAA-MM-DD`. A API **devolve** instantes em UTC (`2026-10-03T17:05:00+00:00`). Nos\nfiltros você pode **enviar** com qualquer fuso (`-03:00`, `Z`, frações de segundo); sem fuso → 400\n`parametro_invalido`. Na query string, o sinal `+` de um fuso positivo tem de ir codificado como `%2B`\n(`atualizado_desde=2026-10-01T05:30:00%2B02:00`): `+` cru chega como espaço e a API recusa com 400.\n\n**Erros.** Sempre JSON `{ \"erro\": { \"codigo\", \"mensagem\", \"campo\"?, \"id_requisicao\" } }`, em português, com código\nestável em `snake_case` (esquema `Erro`). Além dos documentados em cada operação: 405 `metodo_nao_permitido` (com\n`Allow`), 413 `corpo_grande_demais`, 415 `tipo_nao_suportado`, 409 `conflito`/`chave_idempotencia_reutilizada`,\n422 `validacao`, 500 `erro_interno` e 503 `banco_indisponivel` (repita com espera exponencial).\n\n**CORS fechado.** A API é servidor-a-servidor: nenhuma resposta traz `Access-Control-*`; não a chame do navegador.\nGuarde o token num cofre de segredos, um token por sistema, e revogue em Configurações › API quando o sistema sair\ndo ar. Quem criou o token deixou de ser admin da empresa → 403 `token_sem_dono`.\n",
    "contact": {
      "name": "Central de ajuda do Prodio",
      "url": "https://ajuda.prodio.com.br"
    }
  },
  "servers": [
    {
      "url": "https://api.prodio.com.br",
      "description": "Produção. Os caminhos já levam o `/v1`."
    }
  ],
  "security": [
    {
      "token": []
    }
  ],
  "tags": [
    {
      "name": "Empresa e token",
      "description": "Quem sou eu — a empresa do token e o próprio token. Funciona com qualquer token."
    },
    {
      "name": "Locais",
      "description": "Locais de estoque (fábrica, terceiros, depósitos)."
    },
    {
      "name": "Produtos",
      "description": "Cadastro de produtos e kits, com custo da ficha."
    },
    {
      "name": "Insumos",
      "description": "Cadastro de insumos (matéria-prima), com saldo e custo médio."
    },
    {
      "name": "Fornecedores",
      "description": "Cadastro de fornecedores. O contato (pessoa) não é exposto."
    },
    {
      "name": "Fichas técnicas",
      "description": "Ficha técnica ativa de cada produto."
    },
    {
      "name": "Estoque",
      "description": "Saldos por local e o ledger de movimentos."
    }
  ],
  "paths": {
    "/v1/eu": {
      "get": {
        "operationId": "obterEu",
        "summary": "Empresa e token da chamada",
        "description": "Use para confirmar que o token está certo e descobrir a empresa, o fuso, a hora de virada do dia de produção e os escopos do token.",
        "tags": [
          "Empresa e token"
        ],
        "x-prodio-escopo": "qualquer",
        "security": [
          {
            "token": []
          }
        ],
        "responses": {
          "200": {
            "description": "Empresa e token.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Eu"
                },
                "example": {
                  "empresa": {
                    "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000001",
                    "nome": "Confecção Aurora",
                    "cnpj": "11222333000181",
                    "fuso": "America/Sao_Paulo",
                    "hora_virada": "06:00",
                    "sku_prefixo": "AU"
                  },
                  "token": {
                    "prefixo": "prodio_live_Ab12Cd34",
                    "nome": "Sistema de loja",
                    "escopos": [
                      "produtos:ler",
                      "estoque:ler"
                    ],
                    "expira_em": "2027-10-03T03:00:00+00:00",
                    "limite_por_minuto": 120,
                    "limite_empresa_por_minuto": 300
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/locais": {
      "get": {
        "operationId": "listarLocais",
        "summary": "Locais de estoque",
        "description": "Todos os locais da empresa, ordenados por nome, no envelope de lista. Não há paginação aqui — `proximo_cursor` é sempre `null`.",
        "tags": [
          "Locais"
        ],
        "x-prodio-escopo": "qualquer",
        "security": [
          {
            "token": []
          }
        ],
        "responses": {
          "200": {
            "description": "Locais da empresa.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaDeLocais"
                },
                "example": {
                  "dados": [
                    {
                      "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000010",
                      "nome": "Fábrica",
                      "tipo": "fabrica",
                      "ativo": true
                    },
                    {
                      "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000011",
                      "nome": "Depósito da loja",
                      "tipo": "deposito",
                      "ativo": true
                    }
                  ],
                  "proximo_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/produtos": {
      "get": {
        "operationId": "listarProdutos",
        "summary": "Listar produtos",
        "description": "Produtos e kits da empresa, em ordem crescente de `atualizado_em` com desempate por `id`. Excluídos só com `incluir_excluidos=true` (vêm com `excluido_em`).",
        "tags": [
          "Produtos"
        ],
        "x-prodio-escopo": "produtos:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/limite"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/atualizado_desde"
          },
          {
            "$ref": "#/components/parameters/sku"
          },
          {
            "$ref": "#/components/parameters/status_produto"
          },
          {
            "$ref": "#/components/parameters/familia"
          },
          {
            "$ref": "#/components/parameters/tipo_produto"
          },
          {
            "$ref": "#/components/parameters/incluir_excluidos"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de produtos.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaDeProdutos"
                },
                "example": {
                  "dados": [
                    {
                      "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000101",
                      "sku": "CAM-AZUL-M",
                      "nome": "Camiseta básica azul M",
                      "familia": "Camisetas",
                      "atributos": {
                        "cor": "Azul",
                        "tamanho": "M"
                      },
                      "ean": "7891234567890",
                      "ncm": "61091000",
                      "status": "ativo",
                      "tipo": "produto",
                      "apelidos": [
                        "CAMAZM-ML",
                        "CAMAZM-SHOPEE"
                      ],
                      "tem_ficha": true,
                      "peso_kg": 0.18,
                      "peso_cubado_kg": 0.3,
                      "largura_cm": 30,
                      "altura_cm": 2,
                      "comprimento_cm": 40,
                      "ipi_pct": 0,
                      "icms_pct": null,
                      "importado": false,
                      "variacao": {
                        "grupo_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000100",
                        "base": false,
                        "eixos": [
                          "cor",
                          "tamanho"
                        ]
                      },
                      "custo_ficha": 12.35,
                      "custo_ficha_bruto": 13.1,
                      "custo_manual": null,
                      "criado_em": "2026-09-21T12:00:00+00:00",
                      "atualizado_em": "2026-10-03T14:05:00+00:00",
                      "excluido_em": null
                    }
                  ],
                  "proximo_cursor": "eyJ1IjoiMjAyNi0xMC0wM1QxNDowNTowMC4wMDAwMDBaIiwiaSI6IjBhN2YxYzJlLTVkM2ItNGY4YS05ZTIxLTAwMDAwMDAwMDEwMSJ9"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/produtos/{id}": {
      "get": {
        "operationId": "obterProduto",
        "summary": "Obter um produto",
        "description": "O produto pelo id, inclusive excluído (com `excluido_em`). Id de outra empresa → 404, como id inexistente.",
        "tags": [
          "Produtos"
        ],
        "x-prodio-escopo": "produtos:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdNoCaminho"
          }
        ],
        "responses": {
          "200": {
            "description": "O produto.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Produto"
                },
                "examples": {
                  "produto": {
                    "$ref": "#/components/examples/produto"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/insumos": {
      "get": {
        "operationId": "listarInsumos",
        "summary": "Listar insumos",
        "description": "Insumos da empresa, em ordem crescente de `atualizado_em` com desempate por `id`. `saldo` é a soma de todos os locais; por local use `/v1/estoque/saldos`.",
        "tags": [
          "Insumos"
        ],
        "x-prodio-escopo": "insumos:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/limite"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/atualizado_desde"
          },
          {
            "$ref": "#/components/parameters/sku"
          },
          {
            "$ref": "#/components/parameters/ativo"
          },
          {
            "$ref": "#/components/parameters/familia"
          },
          {
            "$ref": "#/components/parameters/incluir_excluidos"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de insumos.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaDeInsumos"
                },
                "example": {
                  "dados": [
                    {
                      "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000201",
                      "sku": "MAL-AZ",
                      "nome": "Malha fio 30 azul",
                      "familia": "Malhas",
                      "ativo": true,
                      "unidade_compra": "kg",
                      "unidade_consumo": "m",
                      "fator_conversao": 3.2,
                      "ncm": "60062200",
                      "minimo": 40,
                      "lead_time_dias": 7,
                      "fornecedor_padrao_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000301",
                      "saldo": 130.5,
                      "custo_medio": 31.2,
                      "custo_medio_bruto": 34.8,
                      "custo_referencia": null,
                      "criado_em": "2026-09-21T12:00:00+00:00",
                      "atualizado_em": "2026-10-03T13:40:12+00:00",
                      "excluido_em": null
                    }
                  ],
                  "proximo_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/insumos/{id}": {
      "get": {
        "operationId": "obterInsumo",
        "summary": "Obter um insumo",
        "description": "O insumo pelo id, inclusive excluído (com `excluido_em`). Id de outra empresa → 404.",
        "tags": [
          "Insumos"
        ],
        "x-prodio-escopo": "insumos:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdNoCaminho"
          }
        ],
        "responses": {
          "200": {
            "description": "O insumo.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Insumo"
                },
                "examples": {
                  "insumo": {
                    "$ref": "#/components/examples/insumo"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/fornecedores": {
      "get": {
        "operationId": "listarFornecedores",
        "summary": "Listar fornecedores",
        "description": "Fornecedores da empresa, em ordem crescente de `atualizado_em` com desempate por `id`. O `contato` (nome e telefone de pessoa) e o e-mail de XML não são expostos.",
        "tags": [
          "Fornecedores"
        ],
        "x-prodio-escopo": "fornecedores:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/limite"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/atualizado_desde"
          },
          {
            "$ref": "#/components/parameters/ativo"
          },
          {
            "$ref": "#/components/parameters/cnpj"
          },
          {
            "$ref": "#/components/parameters/incluir_excluidos"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de fornecedores.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaDeFornecedores"
                },
                "example": {
                  "dados": [
                    {
                      "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000301",
                      "nome": "Malharia Sul",
                      "cnpj": "11222333000181",
                      "regime": "simples",
                      "uf": "SC",
                      "lead_time_dias": 10,
                      "condicao_pagamento": [
                        30,
                        60
                      ],
                      "ativo": true,
                      "criado_em": "2026-09-21T12:00:00+00:00",
                      "atualizado_em": "2026-09-30T09:12:40+00:00",
                      "excluido_em": null
                    }
                  ],
                  "proximo_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/fornecedores/{id}": {
      "get": {
        "operationId": "obterFornecedor",
        "summary": "Obter um fornecedor",
        "description": "O fornecedor pelo id, inclusive excluído (com `excluido_em`). Id de outra empresa → 404.",
        "tags": [
          "Fornecedores"
        ],
        "x-prodio-escopo": "fornecedores:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdNoCaminho"
          }
        ],
        "responses": {
          "200": {
            "description": "O fornecedor.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Fornecedor"
                },
                "examples": {
                  "fornecedor": {
                    "$ref": "#/components/examples/fornecedor"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/fichas": {
      "get": {
        "operationId": "listarFichas",
        "summary": "Listar fichas técnicas ativas",
        "description": "A ficha **ativa** de cada produto, em ordem crescente de `atualizado_em` (o instante em que a versão foi criada) com desempate pelo id da versão. Produto sem ficha ativa não aparece.",
        "tags": [
          "Fichas técnicas"
        ],
        "x-prodio-escopo": "fichas:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/limite"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/atualizado_desde"
          },
          {
            "$ref": "#/components/parameters/produto_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de fichas.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaDeFichas"
                },
                "example": {
                  "dados": [
                    {
                      "produto_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000101",
                      "versao": 3,
                      "ativa": true,
                      "atualizado_em": "2026-10-01T16:30:00+00:00",
                      "linhas": [
                        {
                          "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000401",
                          "tipo": "insumo",
                          "insumo_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000201",
                          "consumo": 0.85,
                          "unidade": "m",
                          "perda_pct": 0.03
                        },
                        {
                          "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000402",
                          "tipo": "produto",
                          "componente_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000103",
                          "consumo": 1,
                          "unidade": "un",
                          "perda_pct": 0
                        }
                      ],
                      "custo": 12.3456
                    }
                  ],
                  "proximo_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/fichas/{produto_id}": {
      "get": {
        "operationId": "obterFicha",
        "summary": "Obter a ficha técnica de um produto",
        "description": "A ficha ativa do produto. Produto inexistente, de outra empresa ou sem ficha ativa → 404 `nao_encontrado`.",
        "tags": [
          "Fichas técnicas"
        ],
        "x-prodio-escopo": "fichas:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ProdutoIdNoCaminho"
          }
        ],
        "responses": {
          "200": {
            "description": "A ficha ativa.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ficha"
                },
                "examples": {
                  "ficha": {
                    "$ref": "#/components/examples/ficha"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/estoque/saldos": {
      "get": {
        "operationId": "listarSaldos",
        "summary": "Listar saldos por local",
        "description": "Uma linha por insumo e local com saldo, em ordem crescente de `atualizado_em` com desempate por insumo e local (o cursor carrega os três).",
        "tags": [
          "Estoque"
        ],
        "x-prodio-escopo": "estoque:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/limite"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/atualizado_desde"
          },
          {
            "$ref": "#/components/parameters/insumo_id"
          },
          {
            "$ref": "#/components/parameters/local_id"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de saldos.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaDeSaldos"
                },
                "example": {
                  "dados": [
                    {
                      "insumo_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000201",
                      "local_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000010",
                      "saldo": 118.5,
                      "unidade": "m",
                      "custo_medio": 31.2,
                      "valor": 3697.2,
                      "atualizado_em": "2026-10-03T13:40:12+00:00"
                    },
                    {
                      "insumo_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000201",
                      "local_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000011",
                      "saldo": 12,
                      "unidade": "m",
                      "custo_medio": 31.2,
                      "valor": 374.4,
                      "atualizado_em": "2026-10-03T13:40:12+00:00"
                    }
                  ],
                  "proximo_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/v1/estoque/movimentos": {
      "get": {
        "operationId": "listarMovimentos",
        "summary": "Listar movimentos de estoque",
        "description": "O ledger de movimentos, em ordem crescente de `criado_em` com desempate por `id`; a janela de 30 s vale sobre `criado_em`. `quantidade` é o delta (negativo nas saídas). Quem fez o movimento não é exposto.",
        "tags": [
          "Estoque"
        ],
        "x-prodio-escopo": "estoque:ler",
        "security": [
          {
            "token": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/limite"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/desde"
          },
          {
            "$ref": "#/components/parameters/insumo_id"
          },
          {
            "$ref": "#/components/parameters/local_id"
          },
          {
            "$ref": "#/components/parameters/tipo_movimento"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de movimentos.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaDeMovimentos"
                },
                "example": {
                  "dados": [
                    {
                      "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000501",
                      "insumo_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000201",
                      "local_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000010",
                      "tipo": "entrada_nfe",
                      "quantidade": 160,
                      "unidade": "m",
                      "custo_unitario": 30.78,
                      "referencia": "NF 1234",
                      "motivo": null,
                      "origem": "nfe",
                      "criado_em": "2026-10-02T18:20:05+00:00"
                    },
                    {
                      "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000502",
                      "insumo_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000201",
                      "local_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000010",
                      "tipo": "ajuste",
                      "quantidade": -3.5,
                      "unidade": "m",
                      "custo_unitario": 31.2,
                      "referencia": null,
                      "motivo": "Diferença de contagem",
                      "origem": "api",
                      "criado_em": "2026-10-03T13:40:12+00:00"
                    }
                  ],
                  "proximo_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErroRequisicao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "token": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "prodio_live_… (55 caracteres)",
        "description": "Token da empresa criado em Configurações › API. Só no cabeçalho `Authorization`; nunca na URL."
      }
    },
    "headers": {
      "X-Request-Id": {
        "description": "Id desta requisição (uuid). Vem em toda resposta; cite-o ao pedir suporte.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "X-RateLimit-Limit": {
        "description": "Limite de chamadas por minuto do token (120).",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Chamadas que ainda cabem nesta janela de 1 minuto.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "X-RateLimit-Reset": {
        "description": "Instante Unix (segundos) em que a janela reinicia.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Segundos a esperar antes de repetir a chamada.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "Idempotency-Replayed": {
        "description": "`true` quando a resposta é a repetição de uma escrita anterior com a mesma `Idempotency-Key` e o mesmo corpo (nada foi gravado de novo). Ausente nas demais respostas.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      }
    },
    "parameters": {
      "limite": {
        "name": "limite",
        "in": "query",
        "description": "Linhas por página. Padrão 50, máximo 200. Fora de 1..200 → 400 `parametro_invalido`.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "cursor": {
        "name": "cursor",
        "in": "query",
        "description": "Página seguinte — repita exatamente o `proximo_cursor` recebido. Opaco (base64url); adulterado ou de outra lista → 400 `cursor_invalido`.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{1,400}$"
        }
      },
      "atualizado_desde": {
        "name": "atualizado_desde",
        "in": "query",
        "description": "Só linhas com `atualizado_em` **a partir** deste instante, inclusive. ISO 8601 **com fuso** (`2026-10-01T00:00:00-03:00`,\n`2026-10-01T03:00:00Z`, frações de segundo aceitas); sem fuso, data impossível ou texto → 400 `parametro_invalido`.\nFuso positivo: codifique o `+` como `%2B` na query string (`2026-10-01T05:30:00%2B02:00`), senão ele chega como espaço.\n",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "desde": {
        "name": "desde",
        "in": "query",
        "description": "Só linhas com `criado_em` **a partir** deste instante, inclusive. ISO 8601 **com fuso** (`-03:00`, `Z`); sem fuso → 400\n`parametro_invalido`. Fuso positivo: codifique o `+` como `%2B` na query string.\n",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "dia": {
        "name": "dia",
        "in": "query",
        "description": "Dia de produção, `AAAA-MM-DD`. Onde é opcional, o padrão é o dia de produção atual pelo fuso e pela hora de virada da empresa.",
        "schema": {
          "type": "string",
          "format": "date"
        }
      },
      "incluir_excluidos": {
        "name": "incluir_excluidos",
        "in": "query",
        "description": "`true` traz também os cadastros excluídos, com `excluido_em` preenchido, para o outro sistema apagar do lado dele. Excluir move o `atualizado_em`, então a exclusão aparece na leitura incremental.",
        "schema": {
          "type": "boolean",
          "default": false
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "description": "Chave sua, única por operação (1 a 100 caracteres `[A-Za-z0-9_.:-]`), guardada por 24 h por token. **Obrigatória** nos\n`POST` que criam (movimento de estoque, ordem de compra); opcional nos lotes, nos `PUT`, no estorno e no cancelamento.\nMesma chave e mesmo corpo → a mesma resposta com `Idempotency-Replayed: true`; mesma chave com corpo diferente → 409\n`chave_idempotencia_reutilizada`; ausente onde é obrigatória → 400 `parametro_invalido` com `campo: \"Idempotency-Key\"`.\n",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_.:-]{1,100}$"
        }
      },
      "IdNoCaminho": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Id do registro (uuid). Id de outra empresa responde 404, igual a id inexistente.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ProdutoIdNoCaminho": {
        "name": "produto_id",
        "in": "path",
        "required": true,
        "description": "Id do produto (uuid).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "sku": {
        "name": "sku",
        "in": "query",
        "description": "SKU exato (até 200 caracteres).",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        }
      },
      "familia": {
        "name": "familia",
        "in": "query",
        "description": "Família exata, como está no cadastro.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        }
      },
      "status_produto": {
        "name": "status",
        "in": "query",
        "description": "Situação do produto.",
        "schema": {
          "type": "string",
          "enum": [
            "ativo",
            "inativo"
          ]
        }
      },
      "tipo_produto": {
        "name": "tipo",
        "in": "query",
        "description": "Produto simples ou kit.",
        "schema": {
          "type": "string",
          "enum": [
            "produto",
            "kit"
          ]
        }
      },
      "ativo": {
        "name": "ativo",
        "in": "query",
        "description": "Só ativos (`true`) ou só inativos (`false`).",
        "schema": {
          "type": "boolean"
        }
      },
      "cnpj": {
        "name": "cnpj",
        "in": "query",
        "description": "CNPJ exato. Pontuação é ignorada (`11.222.333/0001-81` acha `11222333000181`); só casa 14 dígitos. Fornecedor pessoa física (CPF, 11 dígitos) nunca é achado por aqui.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        }
      },
      "produto_id": {
        "name": "produto_id",
        "in": "query",
        "description": "Só a ficha deste produto.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "insumo_id": {
        "name": "insumo_id",
        "in": "query",
        "description": "Só este insumo.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "local_id": {
        "name": "local_id",
        "in": "query",
        "description": "Só este local de estoque.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "tipo_movimento": {
        "name": "tipo",
        "in": "query",
        "description": "Tipo do movimento.",
        "schema": {
          "type": "string",
          "enum": [
            "entrada_nfe",
            "entrada_manual",
            "baixa_producao",
            "ajuste",
            "perda",
            "estorno",
            "saldo_inicial"
          ]
        }
      }
    },
    "responses": {
      "ErroRequisicao": {
        "description": "Requisição malformada: `parametro_invalido` (parâmetro desconhecido, repetido, vazio ou fora do formato; `campo` diz qual), `cursor_invalido`, `token_na_url` ou `corpo_invalido` (escritas). Os parâmetros são lidos **depois** de autenticar, então a chamada conta no limite e a resposta traz os `X-RateLimit-*`; só `token_na_url` (recusado antes do token) sai sem eles.",
        "x-prodio-status": 400,
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "erro": {
                "codigo": "parametro_invalido",
                "mensagem": "Parâmetro atualizado_desde inválido: instante ISO 8601 com fuso, como 2026-10-01T00:00:00-03:00.",
                "campo": "atualizado_desde",
                "id_requisicao": "8c1e0d2f-7a4b-4c6d-9e8f-000000000999"
              }
            }
          }
        }
      },
      "NaoAutenticado": {
        "description": "Sem token válido: `token_ausente`, `token_invalido` (fora do formato ou desconhecido), `token_revogado` ou `token_vencido`. Token revogado ou vencido é um token que o banco achou: a primeira recusa conta no limite dele e traz os `X-RateLimit-*`; repetir o mesmo token nos 60 s seguintes é recusado pelo cache do servidor, sem contar e **sem** os `X-RateLimit-*`. `token_ausente` e `token_invalido` saem sempre sem eles (nada para contar).",
        "x-prodio-status": 401,
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "erro": {
                "codigo": "token_ausente",
                "mensagem": "Informe o token no cabeçalho Authorization: Bearer.",
                "id_requisicao": "8c1e0d2f-7a4b-4c6d-9e8f-000000000999"
              }
            }
          }
        }
      },
      "SemPermissao": {
        "description": "Token reconhecido, mas sem direito: `escopo_insuficiente` (falta o escopo da rota), `empresa_suspensa`, `token_sem_dono` (quem criou o token deixou de ser admin da empresa) ou `sem_permissao`. A chamada conta no limite.",
        "x-prodio-status": 403,
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "erro": {
                "codigo": "escopo_insuficiente",
                "mensagem": "O token não tem o escopo necessário para esta rota.",
                "id_requisicao": "8c1e0d2f-7a4b-4c6d-9e8f-000000000999"
              }
            }
          }
        }
      },
      "NaoEncontrado": {
        "description": "`nao_encontrado`: id inexistente **ou de outra empresa** (a API não revela qual). `rota_inexistente` para caminho que não existe.",
        "x-prodio-status": 404,
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "erro": {
                "codigo": "nao_encontrado",
                "mensagem": "Não encontrado.",
                "id_requisicao": "8c1e0d2f-7a4b-4c6d-9e8f-000000000999"
              }
            }
          }
        }
      },
      "LimiteExcedido": {
        "description": "`limite_excedido`: mais de 120 chamadas no minuto com este token ou mais de 300 com a empresa. Espere o `Retry-After` e repita.",
        "x-prodio-status": 429,
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "erro": {
                "codigo": "limite_excedido",
                "mensagem": "Limite de chamadas por minuto excedido. Espere o Retry-After.",
                "id_requisicao": "8c1e0d2f-7a4b-4c6d-9e8f-000000000999"
              }
            }
          }
        }
      }
    },
    "examples": {
      "produto": {
        "summary": "Camiseta básica azul M",
        "value": {
          "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000101",
          "sku": "CAM-AZUL-M",
          "nome": "Camiseta básica azul M",
          "familia": "Camisetas",
          "atributos": {
            "cor": "Azul",
            "tamanho": "M"
          },
          "ean": "7891234567890",
          "ncm": "61091000",
          "status": "ativo",
          "tipo": "produto",
          "apelidos": [
            "CAMAZM-ML",
            "CAMAZM-SHOPEE"
          ],
          "tem_ficha": true,
          "peso_kg": 0.18,
          "peso_cubado_kg": 0.3,
          "largura_cm": 30,
          "altura_cm": 2,
          "comprimento_cm": 40,
          "ipi_pct": 0,
          "icms_pct": null,
          "importado": false,
          "variacao": {
            "grupo_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000100",
            "base": false,
            "eixos": [
              "cor",
              "tamanho"
            ]
          },
          "custo_ficha": 12.35,
          "custo_ficha_bruto": 13.1,
          "custo_manual": null,
          "criado_em": "2026-09-21T12:00:00+00:00",
          "atualizado_em": "2026-10-03T14:05:00+00:00",
          "excluido_em": null
        }
      },
      "insumo": {
        "summary": "Malha fio 30 azul",
        "value": {
          "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000201",
          "sku": "MAL-AZ",
          "nome": "Malha fio 30 azul",
          "familia": "Malhas",
          "ativo": true,
          "unidade_compra": "kg",
          "unidade_consumo": "m",
          "fator_conversao": 3.2,
          "ncm": "60062200",
          "minimo": 40,
          "lead_time_dias": 7,
          "fornecedor_padrao_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000301",
          "saldo": 130.5,
          "custo_medio": 31.2,
          "custo_medio_bruto": 34.8,
          "custo_referencia": null,
          "criado_em": "2026-09-21T12:00:00+00:00",
          "atualizado_em": "2026-10-03T13:40:12+00:00",
          "excluido_em": null
        }
      },
      "fornecedor": {
        "summary": "Malharia Sul",
        "value": {
          "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000301",
          "nome": "Malharia Sul",
          "cnpj": "11222333000181",
          "regime": "simples",
          "uf": "SC",
          "lead_time_dias": 10,
          "condicao_pagamento": [
            30,
            60
          ],
          "ativo": true,
          "criado_em": "2026-09-21T12:00:00+00:00",
          "atualizado_em": "2026-09-30T09:12:40+00:00",
          "excluido_em": null
        }
      },
      "ficha": {
        "summary": "Ficha ativa da camiseta (versão 3)",
        "value": {
          "produto_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000101",
          "versao": 3,
          "ativa": true,
          "atualizado_em": "2026-10-01T16:30:00+00:00",
          "linhas": [
            {
              "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000401",
              "tipo": "insumo",
              "insumo_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000201",
              "consumo": 0.85,
              "unidade": "m",
              "perda_pct": 0.03
            },
            {
              "id": "0a7f1c2e-5d3b-4f8a-9e21-000000000402",
              "tipo": "produto",
              "componente_id": "0a7f1c2e-5d3b-4f8a-9e21-000000000103",
              "consumo": 1,
              "unidade": "un",
              "perda_pct": 0
            }
          ],
          "custo": 12.3456
        }
      }
    },
    "schemas": {
      "Erro": {
        "type": "object",
        "description": "Envelope de todo erro da API. `codigo` é estável; `mensagem` é em português e pode mudar; `campo` aponta o parâmetro, cabeçalho ou chave do corpo recusado.",
        "required": [
          "erro"
        ],
        "properties": {
          "erro": {
            "type": "object",
            "required": [
              "codigo",
              "mensagem",
              "id_requisicao"
            ],
            "properties": {
              "codigo": {
                "type": "string",
                "enum": [
                  "corpo_invalido",
                  "cursor_invalido",
                  "parametro_invalido",
                  "token_na_url",
                  "token_ausente",
                  "token_invalido",
                  "token_revogado",
                  "token_vencido",
                  "escopo_insuficiente",
                  "empresa_suspensa",
                  "token_sem_dono",
                  "sem_permissao",
                  "nao_encontrado",
                  "rota_inexistente",
                  "metodo_nao_permitido",
                  "conflito",
                  "chave_idempotencia_reutilizada",
                  "corpo_grande_demais",
                  "tipo_nao_suportado",
                  "validacao",
                  "limite_excedido",
                  "erro_interno",
                  "banco_indisponivel"
                ]
              },
              "mensagem": {
                "type": "string"
              },
              "campo": {
                "type": "string",
                "description": "Parâmetro, cabeçalho ou chave do corpo (`itens[2].qtd`) a que o erro se refere."
              },
              "id_requisicao": {
                "type": "string",
                "format": "uuid",
                "description": "O mesmo valor do cabeçalho `X-Request-Id`."
              }
            }
          }
        }
      },
      "Eu": {
        "type": "object",
        "description": "A empresa do token e o próprio token (`GET /v1/eu`).",
        "required": [
          "empresa",
          "token"
        ],
        "properties": {
          "empresa": {
            "type": "object",
            "required": [
              "id",
              "nome",
              "cnpj",
              "fuso",
              "hora_virada",
              "sku_prefixo"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "nome": {
                "type": "string"
              },
              "cnpj": {
                "type": "string",
                "description": "Só dígitos."
              },
              "fuso": {
                "type": "string",
                "description": "Fuso IANA da empresa (`America/Sao_Paulo`). As respostas da API vêm em UTC; este é o fuso para interpretar o dia de produção."
              },
              "hora_virada": {
                "type": "string",
                "description": "Hora (`HH:MM`) em que vira o dia de produção, no fuso da empresa."
              },
              "sku_prefixo": {
                "type": "string",
                "description": "Prefixo do SKU automático e das etiquetas."
              }
            }
          },
          "token": {
            "type": "object",
            "required": [
              "prefixo",
              "nome",
              "escopos",
              "expira_em",
              "limite_por_minuto",
              "limite_empresa_por_minuto"
            ],
            "properties": {
              "prefixo": {
                "type": "string",
                "description": "Os 20 primeiros caracteres do token (`prodio_live_` + 8), como aparecem em Configurações › API."
              },
              "nome": {
                "type": "string"
              },
              "escopos": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "expira_em": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "`null` = sem validade."
              },
              "limite_por_minuto": {
                "type": "integer",
                "description": "Chamadas por minuto deste token (120)."
              },
              "limite_empresa_por_minuto": {
                "type": "integer",
                "description": "Chamadas por minuto somando todos os tokens da empresa (300)."
              }
            }
          }
        }
      },
      "Local": {
        "type": "object",
        "required": [
          "id",
          "nome",
          "tipo",
          "ativo"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "fabrica",
              "terceiro",
              "deposito"
            ]
          },
          "ativo": {
            "type": "boolean"
          }
        }
      },
      "Produto": {
        "type": "object",
        "description": "Produto ou kit. Percentuais como fração (`0.05` = 5 %). Campos em reais exigem `custos:ler`. Toda chave vem sempre (com `null` quando não há valor).",
        "required": [
          "id",
          "sku",
          "nome",
          "familia",
          "atributos",
          "ean",
          "ncm",
          "status",
          "tipo",
          "apelidos",
          "tem_ficha",
          "peso_kg",
          "peso_cubado_kg",
          "largura_cm",
          "altura_cm",
          "comprimento_cm",
          "ipi_pct",
          "icms_pct",
          "importado",
          "variacao",
          "custo_ficha",
          "custo_ficha_bruto",
          "custo_manual",
          "criado_em",
          "atualizado_em",
          "excluido_em"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "sku": {
            "type": "string",
            "description": "Letras, números e hífen, até 40."
          },
          "nome": {
            "type": "string"
          },
          "familia": {
            "type": [
              "string",
              "null"
            ]
          },
          "atributos": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Atributos livres do cadastro (`cor`, `tamanho`…)."
          },
          "ean": {
            "type": [
              "string",
              "null"
            ]
          },
          "ncm": {
            "type": [
              "string",
              "null"
            ],
            "description": "8 dígitos."
          },
          "status": {
            "type": "string",
            "enum": [
              "ativo",
              "inativo"
            ]
          },
          "tipo": {
            "type": "string",
            "enum": [
              "produto",
              "kit"
            ]
          },
          "apelidos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "SKUs externos (De-Para dos hubs), em ordem alfabética."
          },
          "tem_ficha": {
            "type": "boolean",
            "description": "Existe uma versão ativa da ficha técnica."
          },
          "peso_kg": {
            "type": [
              "number",
              "null"
            ]
          },
          "peso_cubado_kg": {
            "type": [
              "number",
              "null"
            ]
          },
          "largura_cm": {
            "type": [
              "number",
              "null"
            ]
          },
          "altura_cm": {
            "type": [
              "number",
              "null"
            ]
          },
          "comprimento_cm": {
            "type": [
              "number",
              "null"
            ]
          },
          "ipi_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Fração (`0.05` = 5 %)."
          },
          "icms_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Fração (`0.05` = 5 %)."
          },
          "importado": {
            "type": "boolean"
          },
          "variacao": {
            "type": [
              "object",
              "null"
            ],
            "description": "`null` em produto sem grupo de variação. `base: true` no produto que encabeça o grupo (`grupo_id` igual ao próprio `id`).",
            "required": [
              "grupo_id",
              "base",
              "eixos"
            ],
            "properties": {
              "grupo_id": {
                "type": "string",
                "format": "uuid"
              },
              "base": {
                "type": "boolean"
              },
              "eixos": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "custo_ficha": {
            "type": [
              "number",
              "null"
            ],
            "description": "Custo calculado pela ficha ativa, em reais, arredondado a 2 casas (a mesma conta da tela de fichas). Exige `custos:ler`; `null` também quando não há ficha."
          },
          "custo_ficha_bruto": {
            "type": [
              "number",
              "null"
            ],
            "description": "Custo da ficha com os impostos de entrada, em reais, 2 casas. Exige `custos:ler`."
          },
          "custo_manual": {
            "type": [
              "number",
              "null"
            ],
            "description": "Custo informado à mão, em reais. Exige `custos:ler`."
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time",
            "description": "Base da leitura incremental."
          },
          "excluido_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Preenchido quando o produto foi excluído."
          }
        }
      },
      "Insumo": {
        "type": "object",
        "description": "Insumo (matéria-prima). `saldo` soma todos os locais; por local use `/v1/estoque/saldos`. Toda chave vem sempre.",
        "required": [
          "id",
          "sku",
          "nome",
          "familia",
          "ativo",
          "unidade_compra",
          "unidade_consumo",
          "fator_conversao",
          "ncm",
          "minimo",
          "lead_time_dias",
          "fornecedor_padrao_id",
          "saldo",
          "custo_medio",
          "custo_medio_bruto",
          "custo_referencia",
          "criado_em",
          "atualizado_em",
          "excluido_em"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "sku": {
            "type": "string"
          },
          "nome": {
            "type": "string"
          },
          "familia": {
            "type": [
              "string",
              "null"
            ]
          },
          "ativo": {
            "type": "boolean"
          },
          "unidade_compra": {
            "type": "string",
            "description": "Unidade em que se compra (`kg`, `rolo`…), das Unidades da empresa."
          },
          "unidade_consumo": {
            "type": "string",
            "description": "Unidade em que a ficha consome (`m`, `un`…)."
          },
          "fator_conversao": {
            "type": "number",
            "description": "Quantas unidades de consumo há em uma de compra."
          },
          "ncm": {
            "type": [
              "string",
              "null"
            ]
          },
          "minimo": {
            "type": [
              "number",
              "null"
            ],
            "description": "Estoque mínimo, em unidade de consumo."
          },
          "lead_time_dias": {
            "type": [
              "integer",
              "null"
            ]
          },
          "fornecedor_padrao_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "saldo": {
            "type": "number",
            "description": "Soma dos saldos de todos os locais, em unidade de consumo. Sempre visível (não exige custos:ler)."
          },
          "custo_medio": {
            "type": [
              "number",
              "null"
            ],
            "description": "Custo médio ponderado dos saldos, em reais por unidade de consumo. Exige `custos:ler`."
          },
          "custo_medio_bruto": {
            "type": [
              "number",
              "null"
            ],
            "description": "Custo médio com os impostos de entrada, em reais. Exige `custos:ler`."
          },
          "custo_referencia": {
            "type": [
              "number",
              "null"
            ],
            "description": "Custo de referência do cadastro (usado quando não há saldo), em reais. Exige `custos:ler`."
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          },
          "excluido_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Fornecedor": {
        "type": "object",
        "description": "Fornecedor. O `contato` (nome e telefone de pessoa) e o e-mail de XML **não** são expostos. Toda chave vem sempre.",
        "required": [
          "id",
          "nome",
          "cnpj",
          "regime",
          "uf",
          "lead_time_dias",
          "condicao_pagamento",
          "ativo",
          "criado_em",
          "atualizado_em",
          "excluido_em"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "cnpj": {
            "type": [
              "string",
              "null"
            ],
            "description": "CNPJ só dígitos (14). Fornecedor pessoa física (cadastrado com CPF, 11 dígitos) vem `null`: CPF é dado pessoal e não sai pela API."
          },
          "regime": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "simples",
              "normal",
              null
            ],
            "description": "Regime tributário."
          },
          "uf": {
            "type": [
              "string",
              "null"
            ],
            "description": "UF de origem esperada nas compras."
          },
          "lead_time_dias": {
            "type": [
              "integer",
              "null"
            ]
          },
          "condicao_pagamento": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            },
            "description": "Dias das parcelas (`[30, 60]`)."
          },
          "ativo": {
            "type": "boolean"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          },
          "excluido_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "LinhaDaFicha": {
        "type": "object",
        "description": "Uma linha da ficha. `tipo: insumo` traz `insumo_id`; `tipo: produto` (componente de kit ou semiacabado) traz `componente_id`.",
        "required": [
          "id",
          "tipo",
          "consumo",
          "unidade",
          "perda_pct"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "insumo",
              "produto"
            ]
          },
          "insumo_id": {
            "type": "string",
            "format": "uuid",
            "description": "Só quando `tipo` é `insumo`."
          },
          "componente_id": {
            "type": "string",
            "format": "uuid",
            "description": "Id do produto componente; só quando `tipo` é `produto`."
          },
          "consumo": {
            "type": "number",
            "description": "Quantidade consumida por unidade produzida, na `unidade`."
          },
          "unidade": {
            "type": "string"
          },
          "perda_pct": {
            "type": "number",
            "description": "Perda como fração (`0.03` = 3 %)."
          }
        }
      },
      "Ficha": {
        "type": "object",
        "description": "A ficha técnica ativa de um produto. `atualizado_em` é o instante em que esta versão foi criada.",
        "required": [
          "produto_id",
          "versao",
          "ativa",
          "atualizado_em",
          "linhas",
          "custo"
        ],
        "properties": {
          "produto_id": {
            "type": "string",
            "format": "uuid"
          },
          "versao": {
            "type": "integer"
          },
          "ativa": {
            "type": "boolean"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          },
          "linhas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LinhaDaFicha"
            }
          },
          "custo": {
            "type": [
              "number",
              "null"
            ],
            "description": "Custo da ficha, em reais (4 casas). Exige `custos:ler`."
          }
        }
      },
      "Saldo": {
        "type": "object",
        "description": "Saldo de um insumo em um local. `unidade` é a unidade de consumo do insumo.",
        "required": [
          "insumo_id",
          "local_id",
          "saldo",
          "unidade",
          "custo_medio",
          "valor",
          "atualizado_em"
        ],
        "properties": {
          "insumo_id": {
            "type": "string",
            "format": "uuid"
          },
          "local_id": {
            "type": "string",
            "format": "uuid"
          },
          "saldo": {
            "type": "number"
          },
          "unidade": {
            "type": "string"
          },
          "custo_medio": {
            "type": [
              "number",
              "null"
            ],
            "description": "Custo médio neste local, em reais por unidade. Exige `custos:ler`."
          },
          "valor": {
            "type": [
              "number",
              "null"
            ],
            "description": "`saldo × custo_medio`, em reais. Exige `custos:ler`."
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Movimento": {
        "type": "object",
        "description": "Um movimento do ledger de estoque (append-only). `quantidade` é o delta — negativo nas saídas.",
        "required": [
          "id",
          "insumo_id",
          "local_id",
          "tipo",
          "quantidade",
          "unidade",
          "custo_unitario",
          "referencia",
          "motivo",
          "origem",
          "criado_em"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "insumo_id": {
            "type": "string",
            "format": "uuid"
          },
          "local_id": {
            "type": "string",
            "format": "uuid"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "entrada_nfe",
              "entrada_manual",
              "baixa_producao",
              "ajuste",
              "perda",
              "estorno",
              "saldo_inicial"
            ]
          },
          "quantidade": {
            "type": "number",
            "description": "Delta na unidade de consumo (negativo nas saídas)."
          },
          "unidade": {
            "type": "string"
          },
          "custo_unitario": {
            "type": [
              "number",
              "null"
            ],
            "description": "Custo unitário do movimento, em reais. Exige `custos:ler`."
          },
          "referencia": {
            "type": [
              "string",
              "null"
            ],
            "description": "Documento de origem: `NF 1234`, `OC 1042` ou o serial da etiqueta; `null` quando não há."
          },
          "motivo": {
            "type": [
              "string",
              "null"
            ]
          },
          "origem": {
            "type": "string",
            "enum": [
              "tela",
              "api",
              "producao",
              "nfe",
              "inventario"
            ],
            "description": "Por onde o movimento entrou."
          },
          "criado_em": {
            "type": "string",
            "format": "date-time",
            "description": "Base da ordem e do filtro `desde`."
          }
        }
      },
      "PaginaDeLocais": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Local"
            }
          },
          "proximo_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sempre `null` nesta rota."
          }
        }
      },
      "PaginaDeProdutos": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Produto"
            }
          },
          "proximo_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor da página seguinte; `null` = acabou."
          }
        }
      },
      "PaginaDeInsumos": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Insumo"
            }
          },
          "proximo_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor da página seguinte; `null` = acabou."
          }
        }
      },
      "PaginaDeFornecedores": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Fornecedor"
            }
          },
          "proximo_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor da página seguinte; `null` = acabou."
          }
        }
      },
      "PaginaDeFichas": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Ficha"
            }
          },
          "proximo_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor da página seguinte; `null` = acabou."
          }
        }
      },
      "PaginaDeSaldos": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Saldo"
            }
          },
          "proximo_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor da página seguinte; `null` = acabou."
          }
        }
      },
      "PaginaDeMovimentos": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Movimento"
            }
          },
          "proximo_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor da página seguinte; `null` = acabou."
          }
        }
      }
    }
  }
}
