{
  "openapi": "3.1.0",
  "info": {
    "title": "API RCS Inc. — SMS e RCS",
    "version": "1.0.0",
    "summary": "Envio de SMS e RCS, webhooks de status e atribuição de receita.",
    "description": "Contrato legível por máquina dos endpoints públicos da plataforma RCS Inc. Descreve o mesmo comportamento da documentação em https://www.rcsinc.com.br/api-docs.\n\nAutenticação: header `x-api-key`, com a chave no formato `rcsk_live_…`, gerada dentro do portal e exibida uma única vez. Toda troca é JSON, e as mensagens de erro vêm em português.",
    "contact": {
      "name": "RCS Inc. — time técnico",
      "url": "https://www.rcsinc.com.br/api-docs",
      "email": "vendas@rcsinc.com.br"
    },
    "termsOfService": "https://www.rcsinc.com.br/termos"
  },
  "servers": [
    {
      "url": "https://www.rcsinc.com.br/api/v1",
      "description": "Produção"
    }
  ],
  "tags": [
    { "name": "Mensagens", "description": "Disparo de SMS e RCS." },
    { "name": "Conversões", "description": "Atribuição de receita à mensagem que originou a venda." }
  ],
  "security": [{ "ApiKeyAuth": [] }],
  "paths": {
    "/messages": {
      "post": {
        "tags": ["Mensagens"],
        "operationId": "enviarMensagem",
        "summary": "Enviar mensagem",
        "description": "Dispara SMS ou RCS para um ou mais números. O canal RCS cai automaticamente em SMS quando o número não recebe RCS.\n\nEnvie o header `Idempotency-Key` para que um retry de rede não dispare a mensagem duas vezes.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "UUID único por operação. Mesma chave com o mesmo corpo devolve a resposta original, sem novo envio; mesma chave com corpo diferente retorna 409.",
            "schema": { "type": "string", "format": "uuid" },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EnvioRequest" },
              "examples": {
                "texto": {
                  "summary": "SMS/RCS de texto com webhook de status",
                  "value": {
                    "channel": "RCS",
                    "to": ["+5511999999999"],
                    "text": "Olá! Seu pedido foi confirmado.",
                    "notifyUrl": "https://seusistema.com.br/webhooks/rcs"
                  }
                },
                "cards": {
                  "summary": "RCS rico (cards com botões)",
                  "value": {
                    "channel": "RCS",
                    "to": "+5511999999999",
                    "cards": [
                      {
                        "title": "Black Friday",
                        "description": "50% OFF só hoje",
                        "mediaUrl": "https://cdn.seusite.com.br/promo.jpg",
                        "suggestions": [
                          { "type": "OPEN_URL", "text": "Comprar", "url": "https://loja.com.br/bf" },
                          { "type": "REPLY", "text": "Quero saber mais", "postbackData": "INFO" }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Requisição processada.",
            "headers": {
              "Idempotent-Replayed": {
                "description": "`true` quando a resposta é a repetição de um envio anterior (nada novo foi enviado).",
                "schema": { "type": "string", "enum": ["true", "false"] }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/EnvioResposta" },
                "example": { "ok": true, "sent": 1, "failed": 0, "suppressed": 0, "cost": 0.18 }
              }
            }
          },
          "400": {
            "description": "Corpo inválido ou campos faltando: destinatário fora do padrão E.164, `channel` diferente de SMS/RCS, `notifyUrl` não pública, ou nenhum entre `text`, `mediaUrl` e `cards`.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Erro" },
                "example": { "error": "Informe \"to\" com um ou mais números no formato E.164 (ex.: +5511999999999)." }
              }
            }
          },
          "401": {
            "description": "Chave ausente, inválida ou revogada.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          },
          "402": {
            "description": "Saldo insuficiente ou teto de gasto mensal atingido — nenhuma mensagem foi enviada.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErroSaldo" },
                "example": { "error": "Saldo insuficiente para realizar o envio.", "sent": 0, "failed": 0, "suppressed": 0 }
              }
            }
          },
          "403": {
            "description": "API não habilitada para esta conta.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          },
          "409": {
            "description": "A `Idempotency-Key` enviada já foi usada com um corpo diferente (use uma chave nova), ou o envio com essa mesma chave ainda está em processamento. Os dois casos devolvem 409 e o header `Idempotent-Replayed: true`.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          },
          "429": {
            "description": "Limite de requisições excedido.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          },
          "500": {
            "description": "Falha ao processar o envio.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          }
        }
      }
    },
    "/conversions": {
      "post": {
        "tags": ["Conversões"],
        "operationId": "registrarConversao",
        "summary": "Registrar conversão / receita",
        "description": "Amarra o R$ de cada venda à mensagem que a originou — é o que alimenta a receita atribuída nos relatórios.\n\nInforme ao menos um entre `code`, `campaignId` e `recipient`. Envie `orderId` para evitar contagem duplicada do mesmo pedido.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ConversaoRequest" },
              "example": { "value": 149.9, "code": "aB9xK2", "orderId": "PED-10482" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Conversão registrada. `duplicate: true` quando o mesmo `orderId` já havia sido contado.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConversaoResposta" },
                "example": { "ok": true, "duplicate": false }
              }
            }
          },
          "400": {
            "description": "`value` ausente ou negativo, ou nenhum entre `code`, `campaignId` e `recipient`.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Erro" },
                "example": { "error": "Informe ao menos code, campaignId ou recipient." }
              }
            }
          },
          "401": {
            "description": "Chave ausente, inválida ou revogada.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          },
          "403": {
            "description": "API não habilitada para esta conta.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          },
          "429": {
            "description": "Limite de requisições excedido.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Erro" } } }
          }
        }
      }
    }
  },
  "webhooks": {
    "statusDeMensagem": {
      "post": {
        "summary": "Webhook de status de saída",
        "description": "Quando você passa `notifyUrl` no envio, a plataforma faz um POST para essa URL a cada mudança de status. Responda 2xx para confirmar.\n\nA URL precisa ser pública (http/https) — endereços internos são bloqueados. As chamadas têm timeout curto e são best-effort: se o seu servidor estiver fora do ar, o envio não é afetado.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EventoWebhook" },
              "examples": {
                "entregue": {
                  "summary": "Mensagem entregue",
                  "value": {
                    "event": "DELIVERED",
                    "messageId": "msg_a1b2c3",
                    "providerMessageId": "prov_998877",
                    "recipient": "+5511999999999",
                    "at": "2026-06-20T14:32:10.000Z"
                  }
                },
                "clique": {
                  "summary": "Clique em link ou botão",
                  "value": {
                    "event": "CLICKED",
                    "messageId": "msg_a1b2c3",
                    "providerMessageId": "prov_998877",
                    "recipient": "+5511999999999",
                    "url": "https://loja.com.br/bf",
                    "at": "2026-06-20T14:33:02.000Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Recebimento confirmado pelo seu servidor." }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Chave no formato `rcsk_live_…`, gerada em API & Chaves dentro do portal e exibida uma única vez. Guarde em variável de ambiente; se vazar, revogue e gere outra."
      }
    },
    "schemas": {
      "EnvioRequest": {
        "type": "object",
        "required": ["to"],
        "description": "Informe ao menos um entre `text`, `mediaUrl` e `cards`.",
        "properties": {
          "to": {
            "description": "Destinatário(s) em formato E.164.",
            "oneOf": [
              { "type": "string", "pattern": "^\\+[1-9]\\d{7,14}$" },
              { "type": "array", "items": { "type": "string", "pattern": "^\\+[1-9]\\d{7,14}$" }, "minItems": 1 }
            ],
            "examples": ["+5511999999999", ["+5511999999999", "+5511988888888"]]
          },
          "channel": {
            "type": "string",
            "enum": ["SMS", "RCS"],
            "default": "SMS",
            "description": "Canal de envio. RCS cai automaticamente em SMS quando o número não recebe RCS."
          },
          "text": { "type": "string", "description": "Conteúdo de texto da mensagem." },
          "mediaUrl": { "type": "string", "format": "uri", "description": "URL pública de imagem ou vídeo (RCS)." },
          "cards": {
            "type": "array",
            "description": "Cards RCS ricos. De 2 a 10 cards formam um carrossel.",
            "items": { "$ref": "#/components/schemas/Card" }
          },
          "notifyUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL pública (http/https) que recebe os webhooks de status. Endereços internos são recusados."
          }
        }
      },
      "Card": {
        "type": "object",
        "description": "Card RCS. Todos os campos são opcionais, mas um card sem título e sem mídia não tem o que mostrar.",
        "properties": {
          "title": { "type": "string" },
          "description": { "type": "string" },
          "mediaUrl": { "type": "string", "format": "uri", "description": "Imagem ou vídeo do card." },
          "suggestions": {
            "type": "array",
            "maxItems": 4,
            "description": "Botões/sugestões do card — até 4 por card (limite RBM).",
            "items": { "$ref": "#/components/schemas/Sugestao" }
          }
        }
      },
      "Sugestao": {
        "description": "Botão ou resposta rápida do RCS. O campo obrigatório muda conforme o `type`.",
        "oneOf": [
          {
            "type": "object",
            "title": "REPLY — resposta rápida",
            "required": ["type", "text"],
            "properties": {
              "type": { "const": "REPLY" },
              "text": { "type": "string" },
              "postbackData": { "type": "string", "description": "Identificador devolvido quando o usuário toca." }
            }
          },
          {
            "type": "object",
            "title": "OPEN_URL — abrir link",
            "required": ["type", "text", "url"],
            "properties": {
              "type": { "const": "OPEN_URL" },
              "text": { "type": "string" },
              "url": { "type": "string", "format": "uri" }
            }
          },
          {
            "type": "object",
            "title": "DIAL — ligar",
            "required": ["type", "text", "phoneNumber"],
            "properties": {
              "type": { "const": "DIAL" },
              "text": { "type": "string" },
              "phoneNumber": { "type": "string", "description": "E.164." }
            }
          },
          {
            "type": "object",
            "title": "LOCATION — abrir mapa",
            "required": ["type", "text", "latitude", "longitude"],
            "properties": {
              "type": { "const": "LOCATION" },
              "text": { "type": "string" },
              "latitude": { "type": "number" },
              "longitude": { "type": "number" },
              "label": { "type": "string" }
            }
          },
          {
            "type": "object",
            "title": "CALENDAR — criar evento",
            "required": ["type", "text", "title", "startTime", "endTime"],
            "properties": {
              "type": { "const": "CALENDAR" },
              "text": { "type": "string" },
              "title": { "type": "string" },
              "startTime": { "type": "string", "format": "date-time" },
              "endTime": { "type": "string", "format": "date-time" },
              "description": { "type": "string" }
            }
          }
        ]
      },
      "EnvioResposta": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "sent": { "type": "integer", "description": "Mensagens aceitas no canal." },
          "failed": { "type": "integer", "description": "Mensagens recusadas no envio." },
          "suppressed": {
            "type": "integer",
            "description": "Destinatários pulados por opt-out ou bloqueio — não são cobrados."
          },
          "cost": { "type": "number", "description": "Custo total do disparo, em R$." }
        }
      },
      "ConversaoRequest": {
        "type": "object",
        "required": ["value"],
        "description": "Informe ao menos um entre `code`, `campaignId` e `recipient` para a atribuição.",
        "properties": {
          "value": { "type": "number", "minimum": 0, "description": "Valor da venda em R$." },
          "code": { "type": "string", "description": "Code do short-link que gerou o clique/venda." },
          "campaignId": { "type": "string", "description": "Atribui direto a uma campanha." },
          "recipient": { "type": "string", "description": "Atribui ao número (E.164) que recebeu a mensagem." },
          "orderId": { "type": "string", "description": "Id do pedido — usado para evitar contagem duplicada." }
        }
      },
      "ConversaoResposta": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "duplicate": {
            "type": "boolean",
            "description": "`true` quando o mesmo `orderId` já havia sido registrado — a receita não é contada de novo."
          }
        }
      },
      "EventoWebhook": {
        "type": "object",
        "required": ["event", "messageId", "recipient", "at"],
        "properties": {
          "event": {
            "type": "string",
            "enum": ["SENT", "DELIVERED", "READ", "CLICKED", "FAILED"],
            "description": "SENT: aceita no canal. DELIVERED: chegou ao aparelho. READ: aberta (RCS). CLICKED: tocou em link ou botão. FAILED: não foi possível entregar."
          },
          "messageId": { "type": "string", "description": "Id da mensagem na plataforma." },
          "providerMessageId": { "type": "string", "description": "Id da mesma mensagem no canal de envio — é o \"ID no canal\" que aparece no histórico do portal." },
          "recipient": { "type": "string", "description": "Número do destinatário em E.164." },
          "url": { "type": "string", "format": "uri", "description": "Presente em CLICKED: o link tocado." },
          "at": { "type": "string", "format": "date-time", "description": "Momento do evento (ISO 8601, UTC)." }
        }
      },
      "Erro": {
        "type": "object",
        "required": ["error"],
        "properties": { "error": { "type": "string", "description": "Mensagem de erro em português." } }
      },
      "ErroSaldo": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string" },
          "sent": { "type": "integer" },
          "failed": { "type": "integer" },
          "suppressed": { "type": "integer" }
        }
      }
    }
  }
}
