Developers
Decisão tipada com contrato versionado, rota por confiança e recibo auditável. Crie uma chave jev_sk_* no painel e publique um contrato — o serviço julga, o seu código executa.
quickstartcontratorotasgarantiassombraeventosAPIprivacidade
Quickstart
Toda rota de dados usa Authorization: Bearer jev_sk_…. O inquilino é derivado da chave — tenant_id no corpo é ignorado.
export JEVAAAS_API_KEY=jev_sk_… # chave do painel; o texto claro aparece UMA vez
# julgar contra o contrato publicado (usa a versão vigente)
curl https://jev.vertikon.com.br/v1/decisions/judge \
-H "Authorization: Bearer $JEVAAAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contract":"ticket-router",
"state":{"ticket":{"messages":[{"author":"customer",
"text":"O relatório de notas saiu com a coluna de média zerada. Preciso hoje."}]}},
"state_version":"run_184:step_7"}'
# → {"receipt_id":"rcp_01J9Z…","status":"decided","route":"auto","allow":"support:route",
# "enforced":true,"selected":{"question_id":"fila_principal","choice":"suporte","confidence":0.93},
# "threshold":{"bar":0.75,"collect_floor":0.70,"matched_rule":1,"reason":"fila_principal ==suporte"},
# "usage":{"input_tokens":954,"output_tokens":0},"cost_micros_usd":40,"latency_ms":1630, …}
# toda resposta (inclusive erro) traz X-Request-Id; erro tem corpo {error, code, request_id}import { Jevaas, JevaasError, isActionable, describeRoute } from '@jevaas/sdk'; // npm i @jevaas/sdk
// retry/backoff em 429/502/503/504 e timeout já vêm embutidos
const jevaas = new Jevaas({ apiKey: process.env.JEVAAAS_API_KEY! });
try {
const d = await jevaas.judge({
contract: 'ticket-router', // versão vigente (fixe com contract_version: 3)
state: { ticket: { messages: [{ author: 'customer', text: '…' }] } },
state_version: 'run_184:step_7', // opcional, para rastreio
decision_id: 'tkt_9931', // opcional, idempotente no inquilino
});
console.log(d.receipt_id, d.status, d.route, d.decisive_confidence);
if (isActionable(d)) {
// só aqui: route === 'auto' E enforced === true
await minhaPolitica.executar(d.allow!);
} else {
console.log(describeRoute(d.route, d.status)); // collect_evidence · human_review · abstain
}
} catch (e) {
if (e instanceof JevaasError) console.error(e.status, e.message, 'request:', e.requestId);
}import os
from jevaas import Jevaas, JevaasError, is_actionable, describe_route # pip install jevaas
jev = Jevaas(api_key=os.environ["JEVAAAS_API_KEY"]) # chave jev_sk_* do inquilino
# julga com o contrato publicado (versão vigente, ou fixe com contract_version=3)
try:
r = jev.judge("ticket-router",
{"ticket": {"messages": [{"author": "customer", "text": "..."}]}})
except JevaasError as e:
print(e.status, e.message, "request:", e.request_id, "retryable:", e.retryable)
else:
print(r["receipt_id"], r["route"], r["selected"]["choice"], r["decisive_confidence"])
# o JEVaaS NUNCA executa nada: "allow" é token opaco e quem o mapeia para
# permissão é o motor de política do consumidor.
if is_actionable(r): # route == "auto" e enforced is True
minha_politica.executar(r["allow"])
else:
print(describe_route(r["route"], r["status"]))
# sem contrato publicado, perguntas soltas (sem rotas, sem barra):
a = jev.fanout("texto do ticket",
{"is_urgent": {"type": "noul", "instructions": "O cliente pede urgência?"}})
print(a["answers"][0]["noul"])// SDK Go: github.com/vertikon/jev-sdk embrulha exatamente esta chamada.
// Sem SDK, HTTP puro contra a borda:
corpo := strings.NewReader(`{"contract":"ticket-router",
"state":{"ticket":{"messages":[{"author":"customer","text":"Preciso hoje."}]}},
"state_version":"run_184:step_7"}`)
req, _ := http.NewRequest("POST", "https://jev.vertikon.com.br/v1/decisions/judge", corpo)
req.Header.Set("Authorization", "Bearer "+os.Getenv("JEVAAAS_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("JEVaaS %s: %s", resp.Status, resp.Header.Get("X-Request-Id"))
}
var decisao struct {
ReceiptID string `json:"receipt_id"`
Status string `json:"status"`
Route string `json:"route"` // auto | collect_evidence | human_review | abstain
Allow string `json:"allow"` // token opaco; só vem preenchido na rota auto
Enforced bool `json:"enforced"` // false em mode: shadow — não autoriza agir
}
if err := json.NewDecoder(resp.Body).Decode(&decisao); err != nil {
return err
}
// a única condição que autoriza agir
if decisao.Route == "auto" && decisao.Enforced {
minhaPolitica.Executar(decisao.Allow)
}npm i -g @jevaas/cli
jevaas login jev_sk_… # grava a chave em ~/.jevaas/config.json (chmod 600)
jevaas contract create contracts/ticket-router.v3.yaml # publica (valida no serviço; §5)
jevaas contract mode ticket-router shadow # sombra mede; enforce autoriza
jevaas judge decisao.json --dry-run # imprime o request sem gastar token
jevaas judge decisao.json
# ticket-router@3 enforce internal_write
# status decided · rota auto · allow support:route
# confiança decisiva 0.93 · barra 0.75 · piso 0.70 · regra 1
# recibo rcp_01J9Z… · 1630ms · 40µUSD
# AUTORIZADO: Age, dentro do `allow`.
jevaas receipts --contract ticket-router --route human_review # fila de revisão humana
jevaas receipt label rcp_01J9Z… suporte # insumo da calibração
jevaas calibration ticket-router # acerto × confiança por faixa// Claude Code / Cursor / qualquer cliente MCP — .mcp.json:
{
"mcpServers": {
"jevaas": {
"command": "npx",
"args": ["-y", "@jevaas/mcp"],
"env": { "JEVAAAS_API_KEY": "${JEVAAAS_API_KEY}" }
}
}
}
// O servidor MCP lê a chave do AMBIENTE. Referencie a variável quando o cliente
// expandir; se não expandir, cole a chave do painel no lugar do ${…} no SEU
// arquivo local — que não vai para o repositório.
// tools: jev_judge · jev_contracts · jev_receipts · jev_calibration · jev_contract_create
// jev_judge devolve a decisão crua — leia a rota ANTES de agir; em sombra "enforced" é falseClientes oficiais
npm i @jevaas/sdk (TypeScript, retry/backoff e timeout embutidos) · pip install jevaas (Python, zero dependências) · go get github.com/vertikon/jev-sdk · npm i -g @jevaas/cli · npx -y @jevaas/mcp.
O contrato
Um contrato é um documento versionado — a forma exata que POST /contracts aceita e GET /contracts/{id} devolve. É ele que declara o que pode ser perguntado, o que conta como resposta e quando uma confiança basta para agir. Os arquivos de contracts/ são a mesma coisa em YAML.
{
"id": "ticket-router",
"version": 3, // atribuída pelo serviço a cada publicação
"description": "Triagem de ticket de suporte",
"owner": "plataforma",
"mode": "enforce", // shadow | enforce
"action_class": "internal_write", // read|internal_write|external_write|money|permission|irreversible
"bar": 0.75, // opcional; default = barra da classe da ação
"collect_floor": 0.70, // opcional; default 0.70 — abaixo disso, pessoa
"always_human": false, // opcional; default true só em irreversible
"max_state_chars": 150000, // opcional; recusa ANTES de enviar (§7)
"primary_question": "fila_principal",
"default_route": "collect_evidence", // nunca "auto"
"escalations": { "human_review": "fila-suporte-n2" },
// state_fields: o estado separado por FUNÇÃO, não um blob.
"state_fields": [
{ "path": "ticket.messages", "role": "evidence", "required": true },
{ "path": "options.queues", "role": "option", "required": false }
],
// questions é um OBJETO {id: pergunta}: a chave é o id.
// A ORDEM NÃO É SEMÂNTICA — o recibo ordena por id. A rodada vai em "round".
"questions": {
"fila_principal": { "type": "choice", "round": 0,
"instructions": "Qual equipe deve tratar a solicitação principal?",
"criteria": { "vendas": "…", "suporte": "…", "financeiro": "…",
"indeterminado": "Evidência insuficiente." } },
"pede_humano": { "type": "noul",
"instructions": "O cliente pede atendimento por uma pessoa?" },
"urgencia": { "type": "score",
"instructions": "Qual urgência de resolução é expressa?",
"criteria": ["Não informa prazo.", "Solicita em breve.", "Solicita imediata."] }
},
// routes: avaliadas NA ORDEM, a primeira que casar vence.
"routes": [
{ "when": { "question": "fila_principal", "equals": "indeterminado" },
"route": "abstain", "reason": "fila indeterminada" },
{ "when": { "question": "fila_principal", "equals": "suporte" },
"route": "auto", "allow": "support:route" },
{ "when": { "question": "pede_humano", "noul_at_least": 0.8 },
"route": "human_review", "allow": "support:human" }
]
}mode — medir ou autorizar
shadow julga e mede, mas o recibo vem com enforced: false: o consumidor não está autorizado a agir. enforce autoriza a ação nas rotas auto. Comece em sombra: o caminho para produzir confiança é comparar o julgamento com o que a produção já fazia.
action_class — de quem é a barra
A confiança mínima para agir pertence à classe de consequência da ação, não ao modelo e não ao prompt: read, internal_write, external_write, money, permission, irreversible. Escrever num campo interno e mover dinheiro não podem exigir a mesma evidência. bar e collect_floor são opcionais: sem eles valem a barra e o piso da classe.
routes — a decisão sobre a resposta
As regras são avaliadas na ordem, e a primeira que casar vence. Uma condição combina question com equals, score_at_least, noul_at_least, confidence_at_least, confidence_below ou escape_hatch — todos com E. Quando nenhuma casa, vale default_route, que nunca pode ser auto.
allow é um token opaco: quem o traduz em permissão concreta é o motor de política do consumidor. Não existe campo de execução na resposta — o JEVaaS nunca executa nada.
As quatro rotas
A rota é a instrução. A confiança decide o que fazer com a resposta, e não só qual é a resposta.
| route | Quando acontece | O que o consumidor faz |
|---|---|---|
| auto | Vencedor claro e confiança acima da barra da classe da ação | Age, dentro do allow — só com enforced: true |
| collect_evidence | Faixa do meio (entre collect_floor e bar) | Busca evidência nova e rejulga; não age |
| human_review | Confiança abaixo do piso, ou consequência irreversível | Enfileira para uma pessoa; não age |
| abstain | A saída de escape venceu: nenhuma opção serve | Corrige o menu, não a pergunta; não age |
collect_evidence não é “peça a mesma pergunta de novo”: é buscar evidência nova no estado e rejulgar. Repetir a pergunta com o mesmo estado gasta token e devolve a mesma distribuição.
O status acompanha a rota: decided, abstained, provider_error, stale (o estado mudou — rejulgue), budget_exceeded (429) e pending_reconciliation (timeout depois do envio: o custo pode ter ocorrido — confira o consumo).
O que o serviço recusa
Contrato reprovado não é gravado: volta 422 invalid_contract com a razão. Publicar validação frouxa seria pior que publicar nada — um contrato mal escrito governa produção.
- choice sem saída de escape — sem uma opção como
indeterminadoounenhum, o modelo é obrigado a escolher entre alternativas que podem não servir. Escape hatch é o que transforma evidência insuficiente em abstenção honesta em vez de chute confiante. - choice com menos de 2 ou mais de 254 opções — uma opção não é escolha; 254 é o teto da distribuição.
- score com nível ordinal (
low,médio,3) ou com menos de 2 níveis — nível que não se sustenta sozinho não descreve estado do mundo, então o modelo não tem onde posicionar a evidência. Descreva o que o nível significa. - pergunta sem
instructions— o modelo não vê o id da pergunta:urgencianão instrui nada. - primary_question ausente ou inexistente — o roteamento precisa saber qual resposta governa.
- default_route: “auto” — a rota de saída não pode ser a de agir. Sem isso, um contrato mal escrito autorizaria tudo por omissão.
- regra que mistura primitiva e limiar —
score_at_leastnumachoice,noul_at_leastnumascore,confidence_*numanoul. Onoulnão tem confiança: a probabilidade já é a incerteza entre sim e não. - score_at_least fora da escala declarada, collect_floor acima da bar e regra sem condição — regra sem condição capturaria tudo; use
default_route.
Estas geram aviso (no corpo da resposta e no recibo), nunca erro: opção de choice sem regra própria; barra abaixo da barra inicial da classe; instruções que parecem compostas; noul como pergunta primária; o mesmo allow em rotas de consequências diferentes; e state trazendo conclusão em campo de evidência.
Sombrear antes de autorizar
Ligar um contrato em enforce de primeira é apostar a operação numa barra que ninguém mediu. Em shadow o julgamento vem completo, com enforced: false, e o consumidor segue agindo como sempre agiu — devolvendo o desfecho:
curl https://jev.vertikon.com.br/v1/receipts/rcp_01J9Z…/outcome \
-H "Authorization: Bearer $JEVAAAS_API_KEY" -H "Content-Type: application/json" \
-d '{"production_answer":"suporte","action_taken":true}'
# e, quando houver rótulo humano, ele entra na calibração:
curl https://jev.vertikon.com.br/v1/receipts/rcp_01J9Z…/label \
-H "Authorization: Bearer $JEVAAAS_API_KEY" -H "Content-Type: application/json" \
-d '{"human_label":"suporte","labeled_by":"[email protected]"}'Com isso, GET /calibration/{contract} responde a pergunta que decide onde automatizar — não “a resposta média está certa?”, e sim “as respostas de confiança alta são de fato mais confiáveis nesta carga?”:
{
"contract": "ticket-router", "version": 3, "mode": "enforce",
"total": 412, "labeled": 96, "correct": 88, "accuracy": 0.917,
"by_band": [ // acerto POR FAIXA de confiança
{ "band": "0.90-1.00", "total": 61, "labeled": 40, "correct": 39, "accuracy": 0.975 },
{ "band": "0.75-0.90", "total": 122, "labeled": 38, "correct": 34, "accuracy": 0.895 },
{ "band": "0.70-0.75", "total": 88, "labeled": 12, "correct": 10, "accuracy": 0.833 },
{ "band": "0.00-0.70", "total": 141, "labeled": 6, "correct": 5, "accuracy": 0.833 }
],
"routes": { "auto": 240, "collect_evidence": 96, "human_review": 62, "abstain": 14 },
"divergence": { "shadow_total": 120, "diverged": 17, "rate": 0.142 },
"human_review_rate": 0.150,
"drift": [ { "version": 2, "accuracy": 0.88 }, { "version": 3, "accuracy": 0.917 } ]
}Traduzindo: acima de 0,90 o acerto é 0,975 — ali a automação é segura e a barra pode até descer. Na faixa de 0,70–0,75 ele é 0,833 — ali collect_evidence ou pessoa está fazendo trabalho útil, e a barra está no lugar certo. divergence diz quantas vezes o julgamento discordou do que a produção já fazia; drift mostra se uma versão nova piorou a carga real.
Eventos (NATS JetStream)
Consumidores não precisam falar HTTP com o JEVaaS para observar: assinam o NATS. Envelope padrão da fundação: {event_id, tenant_id, vertical, occurred_at, trace_id, payload} . Stream VTK_JEV (vtk.jev.>, retenção 30d, dedup 24h), publicação sempre com Nats-Msg-Id = event_id.
| Subject | Quando |
|---|---|
vtk.jev.decision.judged | toda decisão com status decided |
vtk.jev.decision.abstained | rota abstain — o menu falhou |
vtk.jev.decision.escalated | rota human_review |
vtk.jev.contract.published | versão nova publicada |
vtk.billing.usage.recorded | liquidação de tokens (rateado no Token) |
Os quatro primeiros são a operação da vertical. O quinto, vtk.billing.usage.recorded, não é um assunto vtk.jev.*: é a liquidação de tokens que o Token rateia — a mesma trilha de custo que aparece em GET /usage.
Referência da API
Superfície completa: POST /decisions/judge · POST /decisions/fanout · GET/POST /contracts · GET/PUT /contracts/{id} · /contracts/{id}/versions · POST /contracts/{id}/versions/{v}/promote · POST /contracts/{id}/mode · GET /receipts · GET /receipts/{id} · POST /receipts/{id}/label · POST /receipts/{id}/outcome · GET /calibration/{contract} · POST /evals/{contract}/run · GET /auth/me · GET /quota · GET /usage · GET /health.
A especificação OpenAPI 3.1 é a fonte dos SDKs e está navegável em Scalar em /docs · /openapi.json (a mesma em https://jev.vertikon.com.br/v1/openapi.json).
Erros e limites
- Corpo de erro sempre
{error, code, request_id}, com o headerX-Request-Idem toda resposta (inclusive erro). Cite-o no suporte; os SDKs expõem emrequestId. 429vem comRetry-After— por rate limit ou por teto de tokens do inquilino (quota_exceeded). Não confunda com “o modelo disse não”.502 provider_erroré a borda de IA falhando, não abstenção:abstainedsignifica que o menu não serve;provider_error, que o menu não foi julgado.504 gateway_timeoutsó acontece antes do envio. Timeout depois do envio devolve200comstatus: pending_reconciliation— o custo pode ter ocorrido: confira o consumo.- O estado é recusado acima de
max_state_chars(default150000) antes de sair: o gateway trunca acima de 2 MiB em silêncio, e truncar evidência em silêncio produz julgamento confiante sobre estado incompleto.
Privacidade e retenção
Chave por inquilino, escopo read | write | admin, e no servidor só existe sha256(chave) — o texto claro aparece uma única vez, na emissão. Um id de contrato ou recibo de outro inquilino devolve 404, e não 403: a existência do recurso alheio não é revelada.
O state julgado vai para a borda de IA como evidência a classificar; a chave do fornecedor vive só no pool do gateway (regra IA-01), nunca no serviço. O recibo guarda o hash do estado, o fingerprint do contrato e o limiar aplicado: a auditoria é reconstruível sem reter o texto do cliente. Contratos e recibos ficam no banco do inquilino, no Brasil.