JEVaaS

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.

curl — POST /decisions/judge
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}
TypeScript — SDK oficial @jevaas/sdk
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);
}
Python — SDK oficial (pip install jevaas)
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"])
Go — github.com/vertikon/jev-sdk (ou HTTP puro)
// 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)
}
CLI — @jevaas/cli
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
MCP — Claude Code, Cursor e afins
// 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" é false

Clientes 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.

ticket-router — documento completo (§3.4 do contrato de borda)
{
  "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.

routeQuando aconteceO que o consumidor faz
autoVencedor claro e confiança acima da barra da classe da açãoAge, dentro do allow — só com enforced: true
collect_evidenceFaixa do meio (entre collect_floor e bar)Busca evidência nova e rejulga; não age
human_reviewConfiança abaixo do piso, ou consequência irreversívelEnfileira para uma pessoa; não age
abstainA saída de escape venceu: nenhuma opção serveCorrige 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.

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:

devolver o desfecho de produção (§4.4)
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?”:

GET /calibration/ticket-router
{
  "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.

SubjectQuando
vtk.jev.decision.judgedtoda decisão com status decided
vtk.jev.decision.abstainedrota abstain — o menu falhou
vtk.jev.decision.escalatedrota human_review
vtk.jev.contract.publishedversão nova publicada
vtk.billing.usage.recordedliquidaçã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

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.