{
  "openapi": "3.0.3",
  "info": {
    "title": "API M3R System",
    "version": "1.0.0",
    "description": "\n# Introdução\n\nA API REST do **M3R System** permite que o sistema da sua empresa (ERP, site, planilhas, automações) **consulte e cadastre dados** da sua conta. Todas as chamadas devem usar **HTTPS** (uma chamada em `http://` é redirecionada com `301` e o corpo de um `POST` se perde), enviam e recebem **JSON (UTF-8)** e são feitas **de servidor para servidor**.\n\n> ### Ainda não tem uma chave de API?\n> Para gerar a chave, acesse **[https://m3rsystem.com.br/configuracoes/api-chaves](https://m3rsystem.com.br/configuracoes/api-chaves)** (é preciso estar logado no M3R System como administrador da empresa).\n> A chave é mostrada **uma única vez**: copie e guarde em local seguro. Se perder, revogue a chave e crie uma nova.\n\n**URL base:** `https://m3rsystem.com.br/api/v1`\n\n# Primeiros passos\n\n1. Gere sua chave em [https://m3rsystem.com.br/configuracoes/api-chaves](https://m3rsystem.com.br/configuracoes/api-chaves).\n2. Guarde a chave numa variável de ambiente — nunca no código-fonte nem em um site/aplicativo público:\n   ```bash\n   export M3R_API_KEY=\"sk_sua_chave_aqui\"\n   ```\n3. Faça a primeira chamada:\n   ```bash\n   curl \"https://m3rsystem.com.br/api/v1/clientes?por_pagina=5\" \\\n     -H \"Authorization: Bearer $M3R_API_KEY\"\n   ```\n\n# Autenticação\n\nToda requisição precisa da sua chave de API no cabeçalho `Authorization: Bearer sk_sua_chave`. O formato, o que a chave identifica e como obtê-la estão logo abaixo.\n\n<!-- ReDoc-Inject: <security-definitions> -->\n\n# Paginação\n\nAs listagens (`GET /clientes` e `GET /agendamentos`) são paginadas com os parâmetros `pagina` (padrão `1`) e `por_pagina` (padrão `20`, **máximo `100`**). A resposta traz `itens`, `pagina`, `por_pagina` e `total`.\n\n* Valores inválidos **não geram erro**: são ajustados (`por_pagina=500` vira `100`; `pagina=0` vira `1`; texto vira o padrão).\n* Uma página além do fim devolve `itens: []` com status `200`.\n* A **ordenação é fixa**: clientes por `id` crescente; agendamentos por `data_hora` do mais recente para o mais antigo.\n\n# Datas e horários\n\nDatas e horas usam **ISO 8601 sem indicador de fuso** (ex.: `2026-09-21T09:00:00`). Nos filtros `data_de` e `data_ate` você pode enviar só a data (`2026-09-21`, que equivale a `00:00:00` desse dia) ou data e hora. Por isso, para incluir o **dia inteiro** em `data_ate`, envie `2026-09-21T23:59:59`.\n\n# Limites de uso\n\nO limite atual é de **300 requisições por hora, por endereço IP e por endpoint**. As respostas trazem os cabeçalhos `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset` — tratados como **indicativos**, porque a contagem é feita por processo do servidor de aplicação (o limite efetivo pode ser um pouco maior que o informado — não dependa disso). Ao exceder o limite a API responde `429` com o cabeçalho `Retry-After` (segundos). **Espere esse tempo antes de tentar de novo** e evite laços de repetição sem pausa.\n\n**Proteção contra chave inválida:** mais de **20 tentativas por minuto com chave ausente ou inválida**, vindas do mesmo IP, bloqueiam temporariamente esse IP — o bloqueio vale até para uma chave válida, e a resposta também é `429` com `Retry-After`. Se você receber `401` repetidas vezes, corrija a chave em vez de insistir. Chamadas com chave válida não gastam essa cota.\n\n# Erros\n\nTodo erro da API tem o mesmo formato JSON, com uma mensagem em português:\n\n```json\n{ \"erro\": \"Cliente não encontrado.\" }\n```\n\n| Status | Quando acontece |\n|---|---|\n| `400` | Dados inválidos (campo obrigatório ausente, tipo ou tamanho errado, CPF/CNPJ com quantidade de dígitos incorreta, data fora do formato ISO, JSON inválido). |\n| `401` | Chave ausente, sem o prefixo `Bearer`, inválida ou revogada. |\n| `403` | A empresa dona da chave está inativa. |\n| `404` | Cliente não encontrado (ou excluído), ou rota inexistente. |\n| `405` | Método HTTP não permitido nesse endpoint (o cabeçalho `Allow` lista os permitidos). |\n| `413` | Corpo da requisição maior que o permitido. |\n| `415` | O `POST` foi enviado sem `Content-Type: application/json`. |\n| `409` | Já existe um cliente com o mesmo CPF/CNPJ (só no `POST /clientes`; a resposta traz o `cliente_id` existente). |\n| `429` | Limite de requisições excedido, ou excesso de tentativas com chave inválida (veja `Retry-After`). |\n| `500` | Erro interno. Tente novamente; se persistir, fale com o suporte. |\n\n# Boas práticas\n\n* **Guarde a chave em variável de ambiente** ou em um cofre de segredos e **nunca** a coloque em código de navegador/aplicativo móvel: a API não aceita chamadas de páginas web (não há CORS) e quem tiver a chave acessa os dados da sua empresa.\n* Crie **uma chave por integração** (dê um nome a cada uma) para poder revogar só a que for comprometida.\n* Trate `429` esperando o `Retry-After` e trate `5xx` com nova tentativa espaçada.\n* Ao paginar, use `por_pagina=100` e pare quando `itens` vier vazio.\n\n# Limitações atuais\n\nPara você planejar a integração, estes são os limites da API hoje:\n\n* Existem **apenas** os endpoints documentados abaixo — **clientes** (listar, consultar por `id`, criar) e **agendamentos** (listar). **Não há** edição, exclusão nem criação de agendamentos por API.\n* **Não há webhooks** para a sua empresa receber eventos; para acompanhar mudanças, consulte a API periodicamente respeitando o limite de uso.\n* A chave **não tem escopos**: toda chave lê clientes e agendamentos e cria clientes. Não há data de expiração — revogue chaves que não usa mais.\n* `GET /clientes` **não tem filtros** de busca (nome, documento, e-mail).\n* As chaves só podem ser criadas e revogadas pela tela em [https://m3rsystem.com.br/configuracoes/api-chaves](https://m3rsystem.com.br/configuracoes/api-chaves), não por API.\n"
  },
  "servers": [
    {
      "url": "https://m3rsystem.com.br/api/v1",
      "description": "Produção"
    }
  ],
  "security": [
    {
      "ChaveDeApi": []
    }
  ],
  "tags": [
    {
      "name": "Clientes",
      "description": "Consulta e cadastro de clientes da sua empresa."
    },
    {
      "name": "Agendamentos",
      "description": "Consulta dos agendamentos (ordens de serviço) da sua empresa."
    }
  ],
  "paths": {
    "/clientes": {
      "get": {
        "tags": [
          "Clientes"
        ],
        "operationId": "listarClientes",
        "summary": "Listar clientes",
        "description": "Devolve os clientes da empresa dona da chave, do `id` mais antigo para o mais novo, em páginas. Clientes excluídos não aparecem. Não há filtros de busca.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Pagina"
          },
          {
            "$ref": "#/components/parameters/PorPagina"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de clientes.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Limite de requisições da janela atual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual (indicativo).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Momento em que a janela reinicia (timestamp Unix).",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaClientes"
                },
                "example": {
                  "itens": [
                    {
                      "id": 12,
                      "tipo_pessoa": "juridica",
                      "razao_social": "Acme Comércio Ltda",
                      "documento": "12345678000199",
                      "telefone": "11999998888",
                      "email": "contato@acme.com.br",
                      "endereco": "Rua das Flores, 100",
                      "cidade": "São Paulo",
                      "uf": "SP",
                      "cep": "01000-000",
                      "tags": "vip,contrato",
                      "criado_em": "2026-09-20T14:30:12",
                      "campos_personalizados": {
                        "Nº do contrato": "C-2041"
                      }
                    }
                  ],
                  "pagina": 1,
                  "por_pagina": 20,
                  "total": 1
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/EmpresaInativa"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl \"https://m3rsystem.com.br/api/v1/clientes?pagina=1&por_pagina=50\" \\\n  -H \"Authorization: Bearer $M3R_API_KEY\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import os\nimport requests\n\nresposta = requests.get(\n    \"https://m3rsystem.com.br/api/v1/clientes\",\n    params={\"pagina\": 1, \"por_pagina\": 50},\n    headers={\"Authorization\": f\"Bearer {os.environ['M3R_API_KEY']}\"},\n    timeout=30,\n)\nresposta.raise_for_status()\nprint(resposta.json())"
          },
          {
            "lang": "JavaScript",
            "label": "Node.js",
            "source": "const resposta = await fetch(\"https://m3rsystem.com.br/api/v1/clientes?pagina=1&por_pagina=50\", {\n  headers: {\n    Authorization: `Bearer ${process.env.M3R_API_KEY}`,\n  },\n});\nif (!resposta.ok) throw new Error(`${resposta.status}: ${await resposta.text()}`);\nconsole.log(await resposta.json());"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "$ch = curl_init(\"https://m3rsystem.com.br/api/v1/clientes?pagina=1&por_pagina=50\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT => 30,\n    CURLOPT_HTTPHEADER => [\"Authorization: Bearer \" . getenv(\"M3R_API_KEY\")],\n]);\n$resposta = curl_exec($ch);\n$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);\ncurl_close($ch);\n\necho $status, PHP_EOL, $resposta;"
          }
        ]
      },
      "post": {
        "tags": [
          "Clientes"
        ],
        "operationId": "criarCliente",
        "summary": "Criar cliente",
        "description": "Cadastra um cliente na empresa dona da chave. O corpo deve ser JSON e a requisição precisa do cabeçalho `Content-Type: application/json`. Informe `razao_social` e **ou** `tipo_pessoa` **ou** um `documento` com 11 (CPF) ou 14 (CNPJ) dígitos.\n\nSe já existir um cliente com o mesmo CPF/CNPJ (comparando só os dígitos, com qualquer máscara), a API responde `409` com o `cliente_id` do existente — assim, se você reenviar um cadastro depois de um erro de rede, não cria duplicado. Clientes sem documento não são verificados. O cadastro é registrado na auditoria da empresa como criado via API, com o nome da chave.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NovoCliente"
              },
              "examples": {
                "pessoa_juridica": {
                  "summary": "Pessoa jurídica (tipo deduzido pelo CNPJ)",
                  "value": {
                    "razao_social": "Acme Comércio Ltda",
                    "documento": "12345678000199",
                    "telefone": "11999998888",
                    "email": "contato@acme.com.br",
                    "cidade": "São Paulo",
                    "uf": "sp",
                    "tags": "vip,contrato"
                  }
                },
                "pessoa_fisica": {
                  "summary": "Pessoa física",
                  "value": {
                    "razao_social": "Maria Souza",
                    "tipo_pessoa": "fisica",
                    "documento": "123.456.789-09",
                    "email": "maria@exemplo.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Cliente criado. Devolve o cliente completo (campos não enviados voltam `null` ou `\"\"`).",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Limite de requisições da janela atual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual (indicativo).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Momento em que a janela reinicia (timestamp Unix).",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Cliente"
                },
                "example": {
                  "id": 12,
                  "tipo_pessoa": "juridica",
                  "razao_social": "Acme Comércio Ltda",
                  "documento": "12345678000199",
                  "telefone": "11999998888",
                  "email": "contato@acme.com.br",
                  "endereco": "Rua das Flores, 100",
                  "cidade": "São Paulo",
                  "uf": "SP",
                  "cep": "01000-000",
                  "tags": "vip,contrato",
                  "criado_em": "2026-09-20T14:30:12",
                  "campos_personalizados": {
                    "Nº do contrato": "C-2041"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Dados inválidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                },
                "examples": {
                  "obrigatorio": {
                    "summary": "Campo obrigatório",
                    "value": {
                      "erro": "Campo 'razao_social' é obrigatório."
                    }
                  },
                  "tipo": {
                    "summary": "Tipo de pessoa indefinido",
                    "value": {
                      "erro": "Campo 'tipo_pessoa' deve ser 'fisica' ou 'juridica' (ou informe um 'documento' válido)."
                    }
                  },
                  "documento": {
                    "summary": "Documento com dígitos a menos/mais",
                    "value": {
                      "erro": "CNPJ inválido — deve ter 14 dígitos."
                    }
                  },
                  "tamanho": {
                    "summary": "Texto maior que o permitido",
                    "value": {
                      "erro": "Campo 'uf' deve ter no máximo 2 caracteres."
                    }
                  },
                  "tipo_do_campo": {
                    "summary": "Tipo de dado errado",
                    "value": {
                      "erro": "Campo 'tags' deve ser um texto."
                    }
                  },
                  "json": {
                    "summary": "JSON malformado",
                    "value": {
                      "erro": "JSON inválido no corpo da requisição."
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Já existe um cliente com este documento. Devolve o `cliente_id` do existente (nada é criado).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErroDocumentoDuplicado"
                },
                "example": {
                  "erro": "Já existe um cliente com este documento.",
                  "cliente_id": 12
                }
              }
            }
          },
          "413": {
            "description": "Corpo da requisição maior que o permitido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                },
                "example": {
                  "erro": "Corpo da requisição maior que o permitido (8 MB)."
                }
              }
            }
          },
          "415": {
            "description": "Faltou o cabeçalho `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                },
                "example": {
                  "erro": "Envie o corpo em JSON com o header 'Content-Type: application/json'."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/EmpresaInativa"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST \"https://m3rsystem.com.br/api/v1/clientes\" \\\n  -H \"Authorization: Bearer $M3R_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"razao_social\": \"Acme Comércio Ltda\", \"documento\": \"12345678000199\", \"email\": \"contato@acme.com.br\"}'"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import os\nimport requests\n\nresposta = requests.post(\n    \"https://m3rsystem.com.br/api/v1/clientes\",\n    headers={\"Authorization\": f\"Bearer {os.environ['M3R_API_KEY']}\"},\n    json={\n        \"razao_social\": \"Acme Comércio Ltda\",\n        \"documento\": \"12345678000199\",\n        \"email\": \"contato@acme.com.br\"\n    },\n    timeout=30,\n)\nresposta.raise_for_status()\nprint(resposta.json())"
          },
          {
            "lang": "JavaScript",
            "label": "Node.js",
            "source": "const resposta = await fetch(\"https://m3rsystem.com.br/api/v1/clientes\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.M3R_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"razao_social\": \"Acme Comércio Ltda\",\n    \"documento\": \"12345678000199\",\n    \"email\": \"contato@acme.com.br\"\n  }),\n});\nif (!resposta.ok) throw new Error(`${resposta.status}: ${await resposta.text()}`);\nconsole.log(await resposta.json());"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "$ch = curl_init(\"https://m3rsystem.com.br/api/v1/clientes\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT => 30,\n    CURLOPT_POST => true,\n    CURLOPT_POSTFIELDS => json_encode([\"razao_social\" => \"Acme Comércio Ltda\", \"documento\" => \"12345678000199\", \"email\" => \"contato@acme.com.br\"], JSON_UNESCAPED_UNICODE),\n    CURLOPT_HTTPHEADER => [\"Authorization: Bearer \" . getenv(\"M3R_API_KEY\"), \"Content-Type: application/json\"],\n]);\n$resposta = curl_exec($ch);\n$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);\ncurl_close($ch);\n\necho $status, PHP_EOL, $resposta;"
          }
        ]
      }
    },
    "/clientes/{cliente_id}": {
      "get": {
        "tags": [
          "Clientes"
        ],
        "operationId": "obterCliente",
        "summary": "Consultar um cliente",
        "description": "Devolve um cliente pelo `id`. Clientes excluídos e ids de outras empresas respondem `404`.",
        "parameters": [
          {
            "name": "cliente_id",
            "in": "path",
            "required": true,
            "description": "Identificador do cliente.",
            "schema": {
              "type": "integer"
            },
            "example": 12
          }
        ],
        "responses": {
          "200": {
            "description": "O cliente.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Limite de requisições da janela atual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual (indicativo).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Momento em que a janela reinicia (timestamp Unix).",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Cliente"
                },
                "example": {
                  "id": 12,
                  "tipo_pessoa": "juridica",
                  "razao_social": "Acme Comércio Ltda",
                  "documento": "12345678000199",
                  "telefone": "11999998888",
                  "email": "contato@acme.com.br",
                  "endereco": "Rua das Flores, 100",
                  "cidade": "São Paulo",
                  "uf": "SP",
                  "cep": "01000-000",
                  "tags": "vip,contrato",
                  "criado_em": "2026-09-20T14:30:12",
                  "campos_personalizados": {
                    "Nº do contrato": "C-2041"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Cliente não encontrado (ou excluído).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                },
                "example": {
                  "erro": "Cliente não encontrado."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/EmpresaInativa"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl \"https://m3rsystem.com.br/api/v1/clientes/12\" \\\n  -H \"Authorization: Bearer $M3R_API_KEY\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import os\nimport requests\n\nresposta = requests.get(\n    \"https://m3rsystem.com.br/api/v1/clientes/12\",\n    headers={\"Authorization\": f\"Bearer {os.environ['M3R_API_KEY']}\"},\n    timeout=30,\n)\nresposta.raise_for_status()\nprint(resposta.json())"
          },
          {
            "lang": "JavaScript",
            "label": "Node.js",
            "source": "const resposta = await fetch(\"https://m3rsystem.com.br/api/v1/clientes/12\", {\n  headers: {\n    Authorization: `Bearer ${process.env.M3R_API_KEY}`,\n  },\n});\nif (!resposta.ok) throw new Error(`${resposta.status}: ${await resposta.text()}`);\nconsole.log(await resposta.json());"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "$ch = curl_init(\"https://m3rsystem.com.br/api/v1/clientes/12\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT => 30,\n    CURLOPT_HTTPHEADER => [\"Authorization: Bearer \" . getenv(\"M3R_API_KEY\")],\n]);\n$resposta = curl_exec($ch);\n$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);\ncurl_close($ch);\n\necho $status, PHP_EOL, $resposta;"
          }
        ]
      }
    },
    "/agendamentos": {
      "get": {
        "tags": [
          "Agendamentos"
        ],
        "operationId": "listarAgendamentos",
        "summary": "Listar agendamentos",
        "description": "Devolve os agendamentos da empresa, do mais recente para o mais antigo (`data_hora`), em páginas. Agendamentos excluídos não aparecem. Os filtros se combinam.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filtra por `status_atendimento`. Valores usuais: `agendado`, `em_andamento`, `finalizado`, `nao_foi_possivel`. Um valor desconhecido devolve lista vazia.",
            "schema": {
              "type": "string"
            },
            "example": "agendado"
          },
          {
            "name": "data_de",
            "in": "query",
            "required": false,
            "description": "Somente agendamentos a partir desta data/hora (inclusive). ISO 8601: `AAAA-MM-DD` ou `AAAA-MM-DDTHH:MM:SS`.",
            "schema": {
              "type": "string"
            },
            "example": "2026-09-01"
          },
          {
            "name": "data_ate",
            "in": "query",
            "required": false,
            "description": "Somente agendamentos até esta data/hora (inclusive). Só a data (`AAAA-MM-DD`) equivale a `00:00:00` — para incluir o dia todo envie `AAAA-MM-DDT23:59:59`.",
            "schema": {
              "type": "string"
            },
            "example": "2026-09-30T23:59:59"
          },
          {
            "$ref": "#/components/parameters/Pagina"
          },
          {
            "$ref": "#/components/parameters/PorPagina"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de agendamentos.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Limite de requisições da janela atual.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requisições restantes na janela atual (indicativo).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Momento em que a janela reinicia (timestamp Unix).",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaAgendamentos"
                },
                "example": {
                  "itens": [
                    {
                      "id": 88,
                      "numero_os": "OS-2026-0001",
                      "cliente_id": 12,
                      "data_hora": "2026-09-21T09:00:00",
                      "status_atendimento": "agendado",
                      "prioridade": "media",
                      "responsavel_contato": "João Silva",
                      "telefone_contato": "11999998888",
                      "endereco": "Rua das Flores, 100",
                      "campos_personalizados": {
                        "Modelo do aparelho": "XR-200"
                      }
                    }
                  ],
                  "pagina": 1,
                  "por_pagina": 20,
                  "total": 1
                }
              }
            }
          },
          "400": {
            "description": "Data fora do formato ISO 8601.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                },
                "examples": {
                  "data_de": {
                    "value": {
                      "erro": "'data_de' deve estar no formato ISO (AAAA-MM-DD)."
                    }
                  },
                  "data_ate": {
                    "value": {
                      "erro": "'data_ate' deve estar no formato ISO (AAAA-MM-DD)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/EmpresaInativa"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl \"https://m3rsystem.com.br/api/v1/agendamentos?status=agendado&data_de=2026-09-01&data_ate=2026-09-30T23:59:59\" \\\n  -H \"Authorization: Bearer $M3R_API_KEY\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import os\nimport requests\n\nresposta = requests.get(\n    \"https://m3rsystem.com.br/api/v1/agendamentos\",\n    params={\"status\": \"agendado\", \"data_de\": \"2026-09-01\", \"data_ate\": \"2026-09-30T23:59:59\"},\n    headers={\"Authorization\": f\"Bearer {os.environ['M3R_API_KEY']}\"},\n    timeout=30,\n)\nresposta.raise_for_status()\nprint(resposta.json())"
          },
          {
            "lang": "JavaScript",
            "label": "Node.js",
            "source": "const resposta = await fetch(\"https://m3rsystem.com.br/api/v1/agendamentos?status=agendado&data_de=2026-09-01&data_ate=2026-09-30T23:59:59\", {\n  headers: {\n    Authorization: `Bearer ${process.env.M3R_API_KEY}`,\n  },\n});\nif (!resposta.ok) throw new Error(`${resposta.status}: ${await resposta.text()}`);\nconsole.log(await resposta.json());"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "$ch = curl_init(\"https://m3rsystem.com.br/api/v1/agendamentos?status=agendado&data_de=2026-09-01&data_ate=2026-09-30T23:59:59\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT => 30,\n    CURLOPT_HTTPHEADER => [\"Authorization: Bearer \" . getenv(\"M3R_API_KEY\")],\n]);\n$resposta = curl_exec($ch);\n$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);\ncurl_close($ch);\n\necho $status, PHP_EOL, $resposta;"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ChaveDeApi": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_…",
        "x-displayName": "Chave de API (Bearer)",
        "description": "Envie a chave de API no cabeçalho `Authorization`, no formato `Authorization: Bearer sk_sua_chave` (o prefixo `Bearer ` é obrigatório; o cabeçalho `X-API-Key` **não** é aceito).\n\n**Como obter a chave:** para gerar a chave de API, acesse **[https://m3rsystem.com.br/configuracoes/api-chaves](https://m3rsystem.com.br/configuracoes/api-chaves)** (é preciso estar logado como administrador da empresa). A chave é mostrada **uma única vez** — se perder, revogue e crie outra.\n\nA chave identifica a sua empresa: você nunca informa o tenant/empresa na requisição, e só enxerga os dados da empresa dona da chave. Uma chave revogada deixa de funcionar imediatamente."
      }
    },
    "parameters": {
      "Pagina": {
        "name": "pagina",
        "in": "query",
        "required": false,
        "description": "Número da página (começa em 1). Valores inválidos são ajustados para 1.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "PorPagina": {
        "name": "por_pagina",
        "in": "query",
        "required": false,
        "description": "Itens por página. Padrão 20, máximo 100 (valores fora do intervalo são ajustados).",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      }
    },
    "responses": {
      "NaoAutenticado": {
        "description": "Chave ausente, sem o prefixo `Bearer`, inválida ou revogada.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "examples": {
              "ausente": {
                "summary": "Sem cabeçalho Authorization",
                "value": {
                  "erro": "Chave de API ausente. Envie 'Authorization: Bearer <chave>'."
                }
              },
              "invalida": {
                "summary": "Chave inválida ou revogada",
                "value": {
                  "erro": "Chave de API inválida ou revogada."
                }
              }
            }
          }
        }
      },
      "EmpresaInativa": {
        "description": "A empresa dona da chave está inativa.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "erro": "Empresa associada a esta chave está inativa."
            }
          }
        }
      },
      "LimiteExcedido": {
        "description": "Limite de requisições excedido (ou excesso de tentativas com chave inválida). Aguarde o tempo indicado em `Retry-After` (segundos).",
        "headers": {
          "Retry-After": {
            "description": "Segundos até poder tentar de novo.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit": {
            "description": "Limite de requisições da janela atual.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "description": "Requisições restantes na janela atual (indicativo).",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "description": "Momento em que a janela reinicia (timestamp Unix).",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "erro": "Limite de requisições excedido. Aguarde e tente novamente."
            }
          }
        }
      }
    },
    "schemas": {
      "Erro": {
        "type": "object",
        "required": [
          "erro"
        ],
        "properties": {
          "erro": {
            "type": "string",
            "description": "Mensagem em português explicando o problema."
          }
        }
      },
      "ErroDocumentoDuplicado": {
        "type": "object",
        "required": [
          "erro",
          "cliente_id"
        ],
        "properties": {
          "erro": {
            "type": "string",
            "description": "Mensagem em português explicando o problema."
          },
          "cliente_id": {
            "type": "integer",
            "description": "Identificador do cliente que já tem esse documento (veja `GET /clientes/{cliente_id}`).",
            "example": 12
          }
        }
      },
      "Cliente": {
        "type": "object",
        "required": [
          "id",
          "tipo_pessoa",
          "razao_social",
          "documento",
          "telefone",
          "email",
          "endereco",
          "cidade",
          "uf",
          "cep",
          "tags",
          "criado_em",
          "campos_personalizados"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identificador do cliente.",
            "example": 12
          },
          "tipo_pessoa": {
            "type": "string",
            "enum": [
              "fisica",
              "juridica"
            ],
            "description": "`fisica` (CPF) ou `juridica` (CNPJ).",
            "example": "juridica"
          },
          "razao_social": {
            "type": "string",
            "description": "Nome do cliente / razão social.",
            "example": "Acme Comércio Ltda"
          },
          "documento": {
            "type": "string",
            "description": "CPF ou CNPJ, exatamente como foi cadastrado (com ou sem máscara).",
            "nullable": true,
            "example": "12345678000199"
          },
          "telefone": {
            "type": "string",
            "description": "Telefone.",
            "nullable": true,
            "example": "11999998888"
          },
          "email": {
            "type": "string",
            "description": "E-mail.",
            "nullable": true,
            "example": "contato@acme.com.br"
          },
          "endereco": {
            "type": "string",
            "description": "Endereço (logradouro).",
            "nullable": true,
            "example": "Rua das Flores, 100"
          },
          "cidade": {
            "type": "string",
            "description": "Cidade.",
            "nullable": true,
            "example": "São Paulo"
          },
          "uf": {
            "type": "string",
            "description": "Sigla do estado (2 letras, maiúsculas).",
            "nullable": true,
            "example": "SP"
          },
          "cep": {
            "type": "string",
            "description": "CEP.",
            "nullable": true,
            "example": "01000-000"
          },
          "tags": {
            "type": "string",
            "description": "Etiquetas em **texto**, separadas por vírgula (não é uma lista).",
            "nullable": true,
            "example": "vip,contrato"
          },
          "criado_em": {
            "type": "string",
            "description": "Data e hora de criação, ISO 8601 em UTC **sem** indicador de fuso.",
            "example": "2026-09-20T14:30:12"
          },
          "campos_personalizados": {
            "type": "object",
            "additionalProperties": true,
            "description": "Campos personalizados da empresa (criados em Configurações > Campos Personalizados), com o **nome do campo** como chave. Só aparecem os que têm valor. Vazio (`{}`) se a empresa não usa.",
            "example": {
              "Nº do contrato": "C-2041"
            }
          }
        }
      },
      "NovoCliente": {
        "type": "object",
        "required": [
          "razao_social"
        ],
        "properties": {
          "razao_social": {
            "type": "string",
            "maxLength": 180,
            "description": "Nome do cliente / razão social. **Obrigatório** (não pode ser vazio)."
          },
          "tipo_pessoa": {
            "type": "string",
            "enum": [
              "fisica",
              "juridica"
            ],
            "description": "`fisica` ou `juridica`. Opcional **se** `documento` tiver 11 (CPF) ou 14 (CNPJ) dígitos; sem ele e sem um `documento` válido a requisição é recusada."
          },
          "documento": {
            "type": "string",
            "maxLength": 18,
            "description": "CPF (11 dígitos) ou CNPJ (14 dígitos), com ou sem máscara — só a quantidade de dígitos é conferida (os dígitos verificadores **não** são validados). É salvo exatamente como enviado. Se `tipo_pessoa` não for informado, é deduzido pelo tamanho."
          },
          "endereco": {
            "type": "string",
            "maxLength": 255,
            "description": "Endereço (logradouro)."
          },
          "cidade": {
            "type": "string",
            "maxLength": 120,
            "description": "Cidade."
          },
          "uf": {
            "type": "string",
            "maxLength": 2,
            "description": "Sigla do estado; é convertida para maiúsculas."
          },
          "cep": {
            "type": "string",
            "maxLength": 9,
            "description": "CEP."
          },
          "telefone": {
            "type": "string",
            "maxLength": 20,
            "description": "Telefone."
          },
          "email": {
            "type": "string",
            "maxLength": 120,
            "description": "E-mail. Não é validado."
          },
          "responsavel_contato": {
            "type": "string",
            "maxLength": 120,
            "description": "Nome do responsável pelo contato."
          },
          "tags": {
            "type": "string",
            "maxLength": 255,
            "description": "Etiquetas em texto, separadas por vírgula (não envie uma lista)."
          }
        }
      },
      "ListaClientes": {
        "type": "object",
        "required": [
          "itens",
          "pagina",
          "por_pagina",
          "total"
        ],
        "properties": {
          "itens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Cliente"
            }
          },
          "pagina": {
            "type": "integer",
            "description": "Página devolvida.",
            "example": 1
          },
          "por_pagina": {
            "type": "integer",
            "description": "Itens por página efetivamente usados (máximo 100).",
            "example": 20
          },
          "total": {
            "type": "integer",
            "description": "Total de clientes (todas as páginas).",
            "example": 1
          }
        }
      },
      "Agendamento": {
        "type": "object",
        "required": [
          "id",
          "numero_os",
          "cliente_id",
          "data_hora",
          "status_atendimento",
          "prioridade",
          "responsavel_contato",
          "telefone_contato",
          "endereco",
          "campos_personalizados"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identificador do agendamento.",
            "example": 88
          },
          "numero_os": {
            "type": "string",
            "description": "Número amigável da ordem de serviço. Nulo se a numeração estiver desligada.",
            "nullable": true,
            "example": "OS-2026-0001"
          },
          "cliente_id": {
            "type": "integer",
            "description": "Identificador do cliente (veja `GET /clientes/{cliente_id}`).",
            "example": 12
          },
          "data_hora": {
            "type": "string",
            "description": "Data e hora do agendamento, ISO 8601 **sem** fuso (horário local da agenda da empresa).",
            "example": "2026-09-21T09:00:00"
          },
          "status_atendimento": {
            "type": "string",
            "description": "Situação do atendimento. Valores usuais: `agendado`, `em_andamento`, `finalizado`, `nao_foi_possivel`.",
            "example": "agendado"
          },
          "prioridade": {
            "type": "string",
            "description": "Prioridade. Valores usuais: `baixa`, `media`, `alta`, `critica`.",
            "example": "media"
          },
          "responsavel_contato": {
            "type": "string",
            "description": "Nome do responsável pelo contato no local.",
            "example": "João Silva"
          },
          "telefone_contato": {
            "type": "string",
            "description": "Telefone do contato.",
            "example": "11999998888"
          },
          "endereco": {
            "type": "string",
            "description": "Endereço do atendimento.",
            "nullable": true,
            "example": "Rua das Flores, 100"
          },
          "campos_personalizados": {
            "type": "object",
            "additionalProperties": true,
            "description": "Campos personalizados da empresa (criados em Configurações > Campos Personalizados), com o **nome do campo** como chave. Só aparecem os que têm valor. Vazio (`{}`) se a empresa não usa.",
            "example": {
              "Modelo do aparelho": "XR-200"
            }
          }
        }
      },
      "ListaAgendamentos": {
        "type": "object",
        "required": [
          "itens",
          "pagina",
          "por_pagina",
          "total"
        ],
        "properties": {
          "itens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Agendamento"
            }
          },
          "pagina": {
            "type": "integer",
            "description": "Página devolvida.",
            "example": 1
          },
          "por_pagina": {
            "type": "integer",
            "description": "Itens por página efetivamente usados (máximo 100).",
            "example": 20
          },
          "total": {
            "type": "integer",
            "description": "Total de agendamentos que atendem aos filtros (todas as páginas).",
            "example": 1
          }
        }
      }
    }
  }
}