Pular para o conteúdo

Enviar feedback pela API

O widget não é o único caminho. Qualquer coisa que faça uma requisição HTTP pode enviar feedback ao Opini, com suas próprias tags (bug, ideia, cobrança) e o contexto que você quiser ver ao lado da mensagem.

Para que serve

  • Seu próprio formulário de feedback. Você já tem um formulário ou uma tela no app e quer as mensagens no Opini.
  • Seu backend e suas ferramentas de suporte. Encaminhe mensagens de uma caixa de suporte, um chatbot, avaliações da loja de apps ou uma pesquisa de cancelamento.
  • Jogos, apps de celular e desktop. Envie feedback direto do app, com plataforma, build ou fase junto.
  • Scripts e importações. Traga feedback que você coletou em outro lugar, ou envie de uma ferramenta de linha de comando.

Qual chave usar

  • Do seu servidor ou de um script: uma chave privada (sk_live_…), enviada no cabeçalho Authorization. Ela pode definir qualquer tag do projeto. Mantenha no servidor; nunca coloque num app que as pessoas baixam nem numa página web.
  • De um app que as pessoas instalam (jogo, celular, desktop): uma chave live (pk_live_…) com Apps e jogos ativado, enviada no corpo. Chaves live são feitas para ir dentro do app, e você decide quais tags elas podem definir.
  • Num site: o widget com uma chave live e o endereço do seu site nas origens permitidas.

Criar a chave

Primeiro, crie as tags que vai enviar em Configurações do projeto → Tags, por exemplo bug, ideia e cobrança. Depois abra Configurações do projeto → Chaves de API e clique em Criar chave.

Para seu servidor ou um script:

  1. Escolha Privada e dê um nome, como caixa-de-suporte.
  2. Copie a chave quando ela aparecer. Ela é mostrada uma vez; gere outra se perder.

Para um app que as pessoas instalam:

  1. Escolha Pública e dê um nome, como app-ios ou build-steam.
  2. Ative Apps e jogos. Pode deixar as origens permitidas vazias se a chave for usada só pelo app.
  3. Em Tags que esta chave pode definir, escolha as tags que as pessoas podem enviar. Deixe todas desmarcadas se o app não deve definir tags.
  4. Defina um limite diário que combine com quantas pessoas usam o app.

Use uma chave por app, plataforma ou integração. Assim dá para trocar ou revogar uma sem mexer nas outras, e a tabela de chaves mostra quando cada uma foi usada pela última vez.

Enviar feedback

Envie um POST para https://opini.dev/api/v1/ingest com um corpo JSON. Uma chave privada vai no cabeçalho Authorization: Bearer sk_live_…; uma chave live vai no corpo como project_key.

jsonc
{
  "project_key": "pk_live_…",   // live keys only; private keys go in the header
  "text": "What the person wrote", // required: 1 to 4,000 characters
  "email": "[email protected]",     // optional: so you can reply
  "name": "Sam",                   // optional
  "tags": ["bug", "crash"],        // optional: up to 10 tag names
  "context": { "platform": "steam" } // optional: up to 16 KB of JSON
}

Uma requisição completa:

bash
curl https://opini.dev/api/v1/ingest \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Exports to CSV keep timing out for our larger workspaces.",
    "email": "[email protected]",
    "tags": ["bug"],
    "context": {
      "source": "support-inbox",
      "plan": "business",
      "ticket": "4821"
    }
  }'

Um 200 significa que o feedback foi salvo e já está na sua lista de Feedback:

json
{ "feedback_public_id": "7k2m9qxa4f", "attachments": { "enabled": true } }

Exemplos

Do seu backend, com uma chave privada (Node):

javascript
// Node, from your backend. The private key stays on the server.
await fetch("https://opini.dev/api/v1/ingest", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.OPINI_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    text: survey.reason,
    email: user.email,
    tags: ["churn"],
    context: { source: "cancel-survey", plan: user.plan },
  }),
})

De um app de desktop ou celular, com uma chave live (Electron, React Native):

javascript
// Any JavaScript runtime without a browser origin: Electron's main
// process, React Native, Node.
await fetch("https://opini.dev/api/v1/ingest", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    project_key: "pk_live_…",
    text: "The export button does nothing on Linux.",
    tags: ["bug"],
    context: { platform: process.platform, version: app.getVersion() },
  }),
})

De um jogo, com uma chave live (Unity, C#):

csharp
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class OpiniFeedback : MonoBehaviour
{
    const string Url = "https://opini.dev/api/v1/ingest";
    const string Key = "pk_live_…"; // a key with "Apps and games" on

    [System.Serializable] class Context { public string platform; public string build; public string level; }
    [System.Serializable] class Body { public string project_key; public string text; public string[] tags; public Context context; }

    public IEnumerator Send(string text, string[] tags, string level)
    {
        var body = new Body {
            project_key = Key,
            text = text,
            tags = tags,
            context = new Context {
                platform = Application.platform.ToString(),
                build = Application.version,
                level = level,
            },
        };
        var json = JsonUtility.ToJson(body);
        using var req = new UnityWebRequest(Url, "POST");
        req.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(json));
        req.downloadHandler = new DownloadHandlerBuffer();
        req.SetRequestHeader("Content-Type", "application/json");
        yield return req.SendWebRequest();
        if (req.result != UnityWebRequest.Result.Success)
            Debug.LogWarning($"Opini: {req.responseCode} {req.downloadHandler.text}");
    }
}

De um jogo, com uma chave live (Godot, GDScript):

gdscript
# Godot 4. Add an HTTPRequest node as a child named "Http".
const URL = "https://opini.dev/api/v1/ingest"
const KEY = "pk_live_…" # a key with "Apps and games" on

func send_feedback(text: String, tags: Array, level: String) -> void:
    var body = {
        "project_key": KEY,
        "text": text,
        "tags": tags,
        "context": {
            "platform": OS.get_name(),
            "build": ProjectSettings.get_setting("application/config/version"),
            "level": level,
        },
    }
    $Http.request(URL, ["Content-Type: application/json"],
        HTTPClient.METHOD_POST, JSON.stringify(body))

Tags

As tags são como você organiza o que chega: dá para filtrar a lista de Feedback por elas. Envie os nomes das tags; maiúsculas não importam, então "Bug" e "bug" são a mesma tag.

  • Cada tag precisa já existir no projeto. Enviar uma tag nunca a cria.
  • Uma chave live só pode usar as tags da lista dela. Uma chave privada pode usar qualquer tag do projeto.
  • Até 10 tags por mensagem.
  • Se alguma tag não for permitida, nada é salvo e a resposta é um 422 citando essas tags, para você perceber enquanto desenvolve em vez de perder feedback sem saber:
http
HTTP/1.1 422 Unprocessable Entity

{ "error": "tags_not_allowed", "tags": ["internal"] }

Use tags para as poucas coisas pelas quais vai filtrar e organizar, como bug, ideia, cobrança ou crash. Se seu formulário pergunta o tipo de feedback, envie a escolha da pessoa como tag. O resto vai no contexto.

Num site, o widget também envia tags. Defina na tag de script ou mude a partir da sua página:

html
<script
  src="https://opini.dev/widget.js"
  data-opini-key="pk_live_…"
  data-opini-tags="web,beta"
  defer
></script>

<script>
  // Later, change the tags sent with the next message:
  window.opini.setTags(["web", "checkout"])
</script>

Contexto

context é qualquer objeto JSON de até 16 KB. O Opini não exige um formato fixo: cada chave aparece em Contexto quando você abre a mensagem, e dá para filtrar por qualquer uma.

  • De um backend: source, plan, account_id, ticket.
  • De um app ou jogo: platform, version, build, device, level.
  • Mantenha plano quando der ({"platform": "steam"}). Objetos aninhados também funcionam; você filtra com pontos, como device.gpu.
  • Use os mesmos nomes de chave em todo lugar de onde envia, para que um filtro encontre todas as mensagens.
  • Não envie senhas, tokens ou nada que as pessoas não esperariam que você guardasse. Seu time consegue ler todos os valores.

Encontrar no Opini

Na lista de Feedback, use o filtro Tag para mostrar mensagens com qualquer uma das tags escolhidas, e o filtro Contexto para mostrar mensagens cujo contexto tem uma chave com um valor, como platform = steam ou level = 3. Os valores são comparados como texto, então 3 combina com o número 3. Os filtros se combinam entre si e com os de estado e data, e ficam no endereço para você compartilhar a visão.

Os mesmos filtros funcionam na API e na sua ferramenta de IA. A ferramenta MCP list_feedback aceita tags, context_key e context_value, então você pode perguntar "quais bugs clientes business relataram esta semana?".

http
GET /api/v1/projects/{project_id}/feedback?tag=bug,crash&context_key=platform&context_value=steam

Respostas e erros

StatusErroO que fazer
200nenhumSalvo.
400missing_originUma chave live foi usada fora de um site e não tem Apps e jogos ativado. Ative, ou envie do seu servidor com uma chave privada.
400invalid_text, invalid_email, context_too_largeO texto deve ter de 1 a 4.000 caracteres, o e-mail deve ser um endereço real e não descartável, e o contexto deve ter menos de 16 KB.
401invalid_keyA chave está errada ou foi revogada.
403origin_not_allowedA requisição veio de um site que não está nas origens permitidas da chave.
422tags_not_allowedAdicione as tags citadas ao projeto ou às tags permitidas da chave, ou pare de enviá-las.
429rate_limited, daily_cap_exceededMensagens demais. Espere e tente de novo; no limite diário, Retry-After diz quando ele zera (meia-noite UTC).

Se uma requisição falhar porque o aparelho está offline, guarde a mensagem e envie na próxima vez que o app abrir.

Manter as chaves seguras

Uma chave privada pode enviar feedback com qualquer tag, então mantenha no servidor, numa variável de ambiente ou num cofre de segredos, e troque se vazar.

Tudo que vai dentro de um app pode ser lido por alguém determinado, então trate uma chave live como pública. Por isso ela só pode enviar feedback, e só com as tags que você permitiu. Para limitar abusos:

  • Defina um limite diário próximo do que você espera. Mensagens extras recebem um 429 em vez de encher sua lista.
  • Permita só as tags que as pessoas devem escolher. Deixe tags internas fora da chave.
  • Se uma chave for abusada, troque e envie a nova na próxima atualização. Revogar a interrompe na hora.
  • Use uma chave separada por app ou plataforma, para que um vazamento não afete o resto.