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çalhoAuthorization. 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:
- Escolha Privada e dê um nome, como caixa-de-suporte.
- Copie a chave quando ela aparecer. Ela é mostrada uma vez; gere outra se perder.
Para um app que as pessoas instalam:
- Escolha Pública e dê um nome, como app-ios ou build-steam.
- Ative Apps e jogos. Pode deixar as origens permitidas vazias se a chave for usada só pelo app.
- 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.
- 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.
{
"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:
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:
{ "feedback_public_id": "7k2m9qxa4f", "attachments": { "enabled": true } }Exemplos
Do seu backend, com uma chave privada (Node):
// 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):
// 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#):
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):
# 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))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, comodevice.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?".
GET /api/v1/projects/{project_id}/feedback?tag=bug,crash&context_key=platform&context_value=steamRespostas e erros
| Status | Erro | O que fazer |
|---|---|---|
200 | nenhum | Salvo. |
400 | missing_origin | Uma 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. |
400 | invalid_text, invalid_email, context_too_large | O 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. |
401 | invalid_key | A chave está errada ou foi revogada. |
403 | origin_not_allowed | A requisição veio de um site que não está nas origens permitidas da chave. |
422 | tags_not_allowed | Adicione as tags citadas ao projeto ou às tags permitidas da chave, ou pare de enviá-las. |
429 | rate_limited, daily_cap_exceeded | Mensagens 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.