{
  "openapi": "3.1.0",
  "info": {
    "title": "aut2fa — API de autenticação",
    "version": "1.0.0",
    "description": "Envie, acompanhe e valide códigos OTP. Referência da API pública implementada.",
    "contact": {
      "url": "https://doc.aut2fa.com"
    }
  },
  "servers": [
    {
      "url": "https://api.aut2fa.com",
      "description": "API pública; o modo é definido pela chave da aplicação."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Envios"
    },
    {
      "name": "Cadastro"
    }
  ],
  "paths": {
    "/v1/send": {
      "post": {
        "tags": [
          "Envios"
        ],
        "operationId": "createSend",
        "summary": "Enviar um código OTP",
        "description": "Aceita o envio para processamento assíncrono. 202 não confirma entrega. Teste sem code usa 000000 e não envia mensagem real. E-mail ainda retorna channel_unavailable.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Até 255 bytes após trim. Reenvie os mesmos bytes do corpo com a mesma chave para evitar um novo envio. Outro corpo retorna 409.",
            "schema": {
              "type": "string",
              "x-max-bytes-after-trim": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSend"
              },
              "example": {
                "to": "+5531999999999",
                "locale": "pt-BR"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Envio aceito, ou envio existente recuperado por idempotência.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Send"
                }
              }
            }
          },
          "401": {
            "description": "Chave ausente, inválida ou revogada.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Sem cota grátis ou créditos.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Chave de idempotência reutilizada com outro corpo.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "JSON ou parâmetros inválidos; canal indisponível.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limite de envios atingido.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Segundos antes da próxima tentativa.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Erro interno.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/send/{id}/status": {
      "get": {
        "tags": [
          "Envios"
        ],
        "operationId": "getSendStatus",
        "summary": "Consultar um envio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID recebido na criação do envio."
          }
        ],
        "responses": {
          "200": {
            "description": "Estados de entrega e verificação e seus timestamps.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Send"
                }
              }
            }
          },
          "401": {
            "description": "Chave ausente, inválida ou revogada.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Envio não encontrado na conta autenticada.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Erro interno.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/send/{id}/verify": {
      "post": {
        "tags": [
          "Envios"
        ],
        "operationId": "verifySend",
        "summary": "Validar um código OTP",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID recebido na criação do envio."
          }
        ],
        "description": "Só valid=true confirma uma autenticação. Código incorreto retorna 200 e consome tentativa. Cinco erros bloqueiam o envio. Código expirado, bloqueado ou já usado retorna valid=false.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyInput"
              },
              "example": {
                "code": "000000"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado da verificação, inclusive código incorreto ou expirado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResult"
                }
              }
            }
          },
          "401": {
            "description": "Chave ausente, inválida ou revogada.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Envio não encontrado na conta autenticada.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Corpo JSON inválido.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Erro interno.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/signup-settings": {
      "get": {
        "tags": [
          "Cadastro"
        ],
        "operationId": "getSignupSettings",
        "summary": "Consultar a cota vigente para novos cadastros",
        "security": [],
        "responses": {
          "200": {
            "description": "A configuração pode mudar; não fixe a oferta na integração.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "free_sends": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "free_sends"
                  ],
                  "example": {
                    "free_sends": 10
                  }
                }
              }
            }
          },
          "500": {
            "description": "Erro interno.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Chave ak_test_… ou ak_live_… criada no painel. Use apenas no servidor."
      }
    },
    "schemas": {
      "CreateSend": {
        "type": "object",
        "required": [
          "to"
        ],
        "properties": {
          "to": {
            "type": "string",
            "description": "Telefone válido em formato internacional E.164, iniciado por +.",
            "example": "+5531999999999"
          },
          "code": {
            "type": "string",
            "pattern": "^(?:[0-9]{4,10})?$",
            "description": "Opcional. Código próprio de 4 a 10 dígitos. Omitido ou vazio gera seis dígitos em live, ou 000000 em test."
          },
          "locale": {
            "type": "string",
            "enum": [
              "pt-BR",
              "en"
            ],
            "description": "Opcional. Usa o idioma da conta quando omitido."
          }
        }
      },
      "Send": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador do envio."
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "sending",
              "sent",
              "delivered",
              "read",
              "failed"
            ]
          },
          "verification": {
            "type": "string",
            "enum": [
              "pending",
              "verified",
              "expired",
              "locked"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "test",
              "live"
            ]
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "description": "Canal escolhido pela plataforma; null antes do roteamento. sandbox em teste."
          },
          "from": {
            "type": [
              "string",
              "null"
            ],
            "description": "Remetente utilizado; null enquanto não definido."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "read_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "verified_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failure_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Motivo da falha, quando presente."
          }
        },
        "required": [
          "id",
          "status",
          "verification",
          "mode",
          "channel",
          "from",
          "expires_at",
          "created_at",
          "sent_at",
          "delivered_at",
          "read_at",
          "verified_at",
          "failed_at",
          "failure_reason"
        ],
        "example": {
          "id": "snd_01m4prwckhd0kyjgv5vw8a5wjr",
          "status": "queued",
          "verification": "pending",
          "mode": "test",
          "channel": null,
          "from": null,
          "expires_at": "2026-10-11T15:10:00Z",
          "created_at": "2026-10-11T15:00:00Z",
          "sent_at": null,
          "delivered_at": null,
          "read_at": null,
          "verified_at": null,
          "failed_at": null,
          "failure_reason": null
        }
      },
      "VerifyInput": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Código informado pela pessoa. Preserve zeros iniciais."
          }
        }
      },
      "VerifyResult": {
        "type": "object",
        "required": [
          "valid",
          "verification",
          "attempts_left"
        ],
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "verification": {
            "type": "string",
            "enum": [
              "pending",
              "verified",
              "expired",
              "locked"
            ]
          },
          "attempts_left": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5,
            "description": "Tentativas restantes após erro. É zero quando a verificação já encerrou, inclusive no sucesso."
          }
        },
        "example": {
          "valid": true,
          "verification": "verified",
          "attempts_left": 0
        }
      },
      "Problem": {
        "type": "object",
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "example": {
          "type": "https://aut2fa.com/docs/errors#invalid_recipient",
          "title": "Unprocessable Entity",
          "status": 422,
          "code": "invalid_recipient",
          "detail": "\"to\" must be a phone number in international format (+5531999999999) or an e-mail address"
        }
      }
    }
  }
}
