Receita: gasto por campanha de um mês
Objetivo: listar o gasto, as conversões e o CPA de cada campanha de um mês, com totais por moeda, sem erro de unidade.
O que você precisa
Seção intitulada “O que você precisa”- Uma chave de API em
OWIEW_API_KEY(veja Início rápido). OWIEW_API_URLapontando parahttps://dash.owiew.com.- Para o
curl:jq. Para JavaScript: Node.js 18 ou superior. Para Python: o pacoterequests.
Como funciona
Seção intitulada “Como funciona”- Troca a chave por um token em
POST /v1/token. - Lê
GET /v1/t/{tenantID}/campaignscomsinceeuntildo mês. - Converte micros com inteiros, soma por moeda e calcula o CPA do total a partir dos totais somados, nunca pela média dos CPAs.
set -euo pipefailSINCE="${1:-2026-09-01}"UNTIL="${2:-2026-09-30}"
TOKEN_JSON=$(curl -sS -X POST "$OWIEW_API_URL/v1/token" \ -H "Authorization: Bearer $OWIEW_API_KEY")TOKEN=$(echo "$TOKEN_JSON" | jq -r .access_token)TENANT=$(echo "$TOKEN_JSON" | jq -r .tenant_id)
curl -sS "$OWIEW_API_URL/v1/t/$TENANT/campaigns?since=$SINCE&until=$UNTIL" \ -H "Authorization: Bearer $TOKEN" > campanhas.json
echo "Por campanha (gasto_micros, conversoes_micros):"jq -r '.campaigns[] | [.campaign_name, .currency, .spend_micros, .conversions_micros] | @tsv' campanhas.json
echo "Totais por moeda:"jq -r '.campaigns | group_by(.currency)[] | {moeda: .[0].currency, gasto: (map(.spend_micros) | add), conv: (map(.conversions_micros) | add)} | "\(.moeda) gasto_micros=\(.gasto) conversoes_micros=\(.conv)"' campanhas.jsonRode com bash gasto-por-campanha.sh 2026-09-01 2026-09-30. O jq usa números de ponto flutuante, exatos até 2^53; para valores maiores use JavaScript ou Python.
JavaScript (fetch)
Seção intitulada “JavaScript (fetch)”const BASE = process.env.OWIEW_API_URL ?? "https://dash.owiew.com";const CHAVE = process.env.OWIEW_API_KEY;if (!CHAVE) throw new Error("Defina OWIEW_API_KEY");const [since = "2026-09-01", until = "2026-09-30"] = process.argv.slice(2);
async function pegarJson(url, opcoes) { const resposta = await fetch(url, opcoes); const corpo = await resposta.json().catch(() => null); if (!resposta.ok) { const e = corpo?.error ?? {}; throw new Error(`HTTP ${resposta.status} ${e.code ?? ""} (trace_id ${e.trace_id ?? "n/d"})`); } return corpo;}
/** Micros em texto "1500,00", arredondando para centavos com inteiros. */function moeda(micros) { const centavos = (BigInt(micros) + 5_000n) / 10_000n; return `${centavos / 100n},${(centavos % 100n).toString().padStart(2, "0")}`;}
/** Conversões em micros para texto com 2 casas. */function conversoes(micros) { return moeda(micros);}
const token = await pegarJson(`${BASE}/v1/token`, { method: "POST", headers: { Authorization: `Bearer ${CHAVE}` },});
const url = `${BASE}/v1/t/${token.tenant_id}/campaigns?since=${since}&until=${until}`;const { campaigns } = await pegarJson(url, { headers: { Authorization: `Bearer ${token.access_token}` },});
const porMoeda = new Map();for (const c of campaigns) { const t = porMoeda.get(c.currency) ?? { gasto: 0n, conv: 0n }; t.gasto += BigInt(c.spend_micros); t.conv += BigInt(c.conversions_micros); porMoeda.set(c.currency, t); const cpa = c.conversions_micros > 0 ? moeda((BigInt(c.spend_micros) * 1_000_000n) / BigInt(c.conversions_micros)) : "sem conversão"; console.log(`${c.campaign_name}\t${c.currency} ${moeda(c.spend_micros)}\t${conversoes(c.conversions_micros)} conv\tCPA ${cpa}`);}
for (const [codigo, t] of porMoeda) { const cpa = t.conv > 0n ? moeda((t.gasto * 1_000_000n) / t.conv) : "sem conversão"; console.log(`TOTAL ${codigo}\t${moeda(t.gasto)}\t${conversoes(t.conv)} conv\tCPA ${cpa}`);}Rode com node gasto-por-campanha.mjs 2026-09-01 2026-09-30.
Python (requests)
Seção intitulada “Python (requests)”import osimport sysfrom collections import defaultdict
import requests
BASE = os.environ.get("OWIEW_API_URL", "https://dash.owiew.com")CHAVE = os.environ["OWIEW_API_KEY"]since = sys.argv[1] if len(sys.argv) > 1 else "2026-09-01"until = sys.argv[2] if len(sys.argv) > 2 else "2026-09-30"
def checar(r): if r.ok: return r.json() try: e = r.json().get("error", {}) except ValueError: e = {} raise SystemExit(f"HTTP {r.status_code} {e.get('code', '')} (trace_id {e.get('trace_id', 'n/d')})")
def moeda(micros): """Micros em texto '1500,00', arredondando para centavos com inteiros.""" centavos = (micros + 5_000) // 10_000 return f"{centavos // 100},{centavos % 100:02d}"
token = checar(requests.post(f"{BASE}/v1/token", headers={"Authorization": f"Bearer {CHAVE}"}, timeout=30))r = requests.get( f"{BASE}/v1/t/{token['tenant_id']}/campaigns", params={"since": since, "until": until}, headers={"Authorization": f"Bearer {token['access_token']}"}, timeout=60,)campanhas = checar(r)["campaigns"]
totais = defaultdict(lambda: [0, 0])for c in campanhas: totais[c["currency"]][0] += c["spend_micros"] totais[c["currency"]][1] += c["conversions_micros"] cpa = moeda(c["spend_micros"] * 1_000_000 // c["conversions_micros"]) if c["conversions_micros"] else "sem conversão" print(f"{c['campaign_name']}\t{c['currency']} {moeda(c['spend_micros'])}\t{moeda(c['conversions_micros'])} conv\tCPA {cpa}")
for codigo, (gasto, conv) in totais.items(): cpa = moeda(gasto * 1_000_000 // conv) if conv else "sem conversão" print(f"TOTAL {codigo}\t{moeda(gasto)}\t{moeda(conv)} conv\tCPA {cpa}")Rode com python gasto_por_campanha.py 2026-09-01 2026-09-30.
Resultado esperado
Seção intitulada “Resultado esperado”Uma linha por campanha e uma linha de total por moeda, no formato (valores de exemplo):
Marca BRL 1500,00 75,00 conv CPA 20,00Remarketing BRL 2500,33 20,50 conv CPA 121,97TOTAL BRL 4000,33 95,50 conv CPA 41,89O CPA do total (41,89) não é a média dos CPAs das campanhas. Ele é o gasto somado dividido pelas conversões somadas.
Se algo der errado
Seção intitulada “Se algo der errado”HTTP 401: a chave não vale ou venceu. Veja Autenticação.HTTP 400 INVALID_WINDOW: datas inválidas. Veja Janelas de data e paginação.- Lista vazia: confira o período e o estado das contas em
/accounts(last_dateelast_observed_at). Veja Unidades e tipos. - Valores 10 mil vezes maiores que o esperado: você tratou micros como centavos.
- Para guardar o resultado, releia pelo menos os últimos 28 dias em cada execução, porque as plataformas revisam números já reportados.