Guia do Corpus

Corpus: de uma chave à primeira consulta

Este guia leva você de um workspace vazio a uma decisão resolvida, sem ajuda além dele. Ele percorre um único exemplo do início ao fim: um expert chamado publication-review que decide quantos revisores um rascunho precisa antes de ser publicado.

Você precisa de uma organização do Corpus com pelo menos um expert publicado e uma versão de documento publicada. Se for montar isso você mesmo, faça primeiro no workspace; este guia começa a partir de uma chave.


1. Gere uma chave

Tudo o que segue é feito no workspace, em corpus.trysenda.xyz, com sessão iniciada como administrador ou proprietário.

Gere uma chave

Abra Chaves na barra lateral da sua organização e gere uma. Dê a ela um nome que diga quem está chamando, não o que ela chama: "review-bot", não "query-expert". O registro de decisões mostra esse nome ao lado de cada chamada que a chave faz. Restrinja-a ao expert publication-review e a uma tag, para que a chave alcance só o que está dentro das duas.

A chave aparece uma única vez, por completo, logo depois de gerada. Copie antes de sair da tela: depois disso o workspace só mostra o prefixo dela (sk_corpus_...). Guarde-a como CORPUS_KEY no seu shell ou no gerenciador de segredos que este ambiente já usa.


2. Uma primeira consulta, via curl

Toda chamada REST leva Authorization: Bearer $CORPUS_KEY. O caminho base é https://corpus.trysenda.xyz/v1.

Liste os experts que sua chave alcança

curl "https://corpus.trysenda.xyz/v1/experts" \
  -H "Authorization: Bearer $CORPUS_KEY"

Isso também confirma que a chave funciona e devolve a versão publicada atual do expert e o graphHash dele, os dois números aos quais uma trace fixa sua conclusão.

{
  "experts": [
    {
      "id": "5d9e2c1a-6f3b-4a7d-9e1c-2b3a4c5d6e7f",
      "name": "publication-review",
      "description": "How many reviewers a draft needs before it can be published.",
      "version": 4,
      "graphHash": "sha256:2f1a9c...",
      "tags": ["editorial"]
    }
  ]
}

Peça reviewer_count sem todos os fatos

curl -X POST "https://corpus.trysenda.xyz/v1/experts/publication-review/query" \
  -H "Authorization: Bearer $CORPUS_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "goal": "reviewer_count",
    "facts": { "audience": "external" }
  }'

O expert precisa saber se o rascunho contém dados de um cliente antes de concluir. Você só forneceu audience, então o resultado é needs_facts, e needs nomeia exatamente o que falta, tipado: um booleano chamado contains_customer_data.

Não há sessão para retomar. Você não repete a mesma chamada; chama de novo com o fato adicionado.

{
  "decisionId": "3c6b1e2d-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
  "outcome": "needs_facts",
  "conclusion": null,
  "needs": [
    {
      "concept": "contains_customer_data",
      "type": { "kind": "boolean" },
      "description": "Whether the draft names or quotes a customer."
    }
  ],
  "reason": null,
  "certainty": null,
  "expert": {
    "id": "5d9e2c1a-6f3b-4a7d-9e1c-2b3a4c5d6e7f",
    "name": "publication-review",
    "version": 4,
    "graphHash": "sha256:2f1a9c..."
  },
  "trace": [
    {
      "id": "s1",
      "parentId": null,
      "kind": "goal",
      "concept": "reviewer_count",
      "provenance": { "kind": "caller", "field": "goal" },
      "expert": "publication-review",
      "expertVersion": 4
    },
    {
      "id": "s2",
      "parentId": "s1",
      "kind": "fact",
      "concept": "audience",
      "value": "external",
      "provenance": { "kind": "caller", "field": "audience" },
      "expert": "publication-review",
      "expertVersion": 4
    },
    {
      "id": "s3",
      "parentId": "s1",
      "kind": "unresolved",
      "concept": "contains_customer_data",
      "provenance": { "kind": "none" },
      "expert": "publication-review",
      "expertVersion": 4
    }
  ],
  "traceHash": "sha256:9b7e21...",
  "inputHash": "sha256:44ad10...",
  "cached": false
}

Pergunte de novo, com o fato fornecido

curl -X POST "https://corpus.trysenda.xyz/v1/experts/publication-review/query" \
  -H "Authorization: Bearer $CORPUS_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "goal": "reviewer_count",
    "facts": {
      "audience": "external",
      "contains_customer_data": "true"
    }
  }'

Desta vez o resultado é resolved, conclusion é "3" (todo valor no fio é uma string; o tipo do concept diz como lê-la), e trace é a derivação completa: a meta, cada fato, a regra que disparou, e seu último passo, um passo de documento que cita publication-policy.md na versão 2. Essa é a citação que a especificação pede: a trace chega até o parágrafo da política em que a regra se apoiou.

{
  "decisionId": "8f0f3b7a-9c2e-4d61-8a3f-1a2b3c4d5e6f",
  "outcome": "resolved",
  "conclusion": "3",
  "needs": [],
  "reason": null,
  "certainty": 1,
  "expert": {
    "id": "5d9e2c1a-6f3b-4a7d-9e1c-2b3a4c5d6e7f",
    "name": "publication-review",
    "version": 4,
    "graphHash": "sha256:2f1a9c..."
  },
  "trace": [
    {
      "id": "s1",
      "parentId": null,
      "kind": "goal",
      "concept": "reviewer_count",
      "provenance": { "kind": "caller", "field": "goal" },
      "expert": "publication-review",
      "expertVersion": 4
    },
    {
      "id": "s2",
      "parentId": "s1",
      "kind": "fact",
      "concept": "audience",
      "value": "external",
      "provenance": { "kind": "caller", "field": "audience" },
      "expert": "publication-review",
      "expertVersion": 4
    },
    {
      "id": "s3",
      "parentId": "s1",
      "kind": "fact",
      "concept": "contains_customer_data",
      "value": "true",
      "provenance": { "kind": "caller", "field": "contains_customer_data" },
      "expert": "publication-review",
      "expertVersion": 4
    },
    {
      "id": "s4",
      "parentId": "s1",
      "kind": "rule",
      "concept": "reviewer_count",
      "value": "3",
      "detail": "audience = external and contains_customer_data",
      "provenance": {
        "kind": "rule",
        "ruleId": "external-customer-data",
        "expert": "publication-review",
        "version": 4
      },
      "expert": "publication-review",
      "expertVersion": 4
    },
    {
      "id": "s5",
      "parentId": "s4",
      "kind": "document",
      "concept": "reviewer_count",
      "detail": "Section 3: drafts naming or quoting a customer need three reviewers.",
      "provenance": {
        "kind": "document",
        "documentId": "7a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
        "version": 2,
        "title": "publication-policy.md"
      },
      "expert": "publication-review",
      "expertVersion": 4
    }
  ],
  "traceHash": "sha256:6e12ab...",
  "inputHash": "sha256:c02fd7...",
  "cached": false
}

Faça exatamente a mesma pergunta de novo

Mesma versão do expert, mesmos fatos: cached agora é true, e traceHash não mudou. O engine não reavaliou; devolveu a decisão já registrada. traceHash é como você perceberia se tivesse reavaliado: uma política mudada ou uma regra mudada muda o hash mesmo quando a conclusão não muda.

{
  "decisionId": "8f0f3b7a-9c2e-4d61-8a3f-1a2b3c4d5e6f",
  "outcome": "resolved",
  "conclusion": "3",
  "needs": [],
  "reason": null,
  "certainty": 1,
  "expert": {
    "id": "5d9e2c1a-6f3b-4a7d-9e1c-2b3a4c5d6e7f",
    "name": "publication-review",
    "version": 4,
    "graphHash": "sha256:2f1a9c..."
  },
  "trace": [ /* identical to the call above */ ],
  "traceHash": "sha256:6e12ab...",
  "inputHash": "sha256:c02fd7...",
  "cached": true
}

Reproduza a decisão

curl -X POST "https://corpus.trysenda.xyz/v1/decisions/8f0f3b7a-9c2e-4d61-8a3f-1a2b3c4d5e6f/replay" \
  -H "Authorization: Bearer $CORPUS_KEY"

Replay executa decisionId de novo contra a mesma versão do expert e as mesmas entradas, e reporta se a trace ainda bate. matches: true aqui significa que nada mudou sob essa decisão desde que foi registrada. Um match falso nomearia o primeiro passo que divergiu, e o que era esperado contra o que foi encontrado.

{
  "matches": true,
  "divergedAt": null,
  "expected": null,
  "actual": null,
  "trace": [ /* identical to the resolved call above */ ]
}

Liste os documentos e encontre um id

curl "https://corpus.trysenda.xyz/v1/documents?title=policy&limit=2" \
  -H "Authorization: Bearer $CORPUS_KEY"

Todo documento tem um id, e get_document, GET /v1/documents/{id}, as versões dele e a publicação o recebem. Liste os documentos publicados no escopo da chave com GET /v1/documents, ordenados por título. title mantém os que têm esse texto no título atual, sem diferenciar maiúsculas, e limit define o tamanho da página: 50 por padrão, 200 no máximo.

next é o cursor da página seguinte. Devolva-o como cursor e repita até que next seja null; o cursor é opaco, então não monte um. Passe q em vez de paginar para buscar nos conteúdos: uma busca é ordenada por relevância, limitada por limit (20 por padrão), nunca é paginada, e cada resultado traz um snippet do conteúdo em volta da correspondência.

{
  "documents": [
    {
      "id": "7a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
      "type": "policy",
      "language": "en",
      "tags": ["editorial"],
      "currentVersion": {
        "version": 2,
        "title": "publication-policy.md",
        "publishedAt": "2026-09-14T10:00:00.000Z"
      }
    },
    {
      "id": "9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
      "type": "policy",
      "language": "en",
      "tags": ["editorial", "legal"],
      "currentVersion": {
        "version": 1,
        "title": "Retention policy",
        "publishedAt": "2026-09-02T08:30:00.000Z"
      }
    }
  ],
  "next": "WyJSZXRlbnRpb24gcG9saWN5IiwiOWM4YjdhNmQtNWU0Zi00YTNiLThjMmQtMWUwZjlhOGI3YzZkIl0"
}

create_document e ask_corpus recebem nomes de etiquetas. GET /v1/tags lista as que estão no escopo da chave, para você não precisar adivinhá-las.

curl "https://corpus.trysenda.xyz/v1/tags" \
  -H "Authorization: Bearer $CORPUS_KEY"
{ "tags": [{ "name": "editorial" }, { "name": "legal" }] }

3. A mesma consulta, a partir do Claude

O Corpus expõe um único endpoint MCP, https://corpus.trysenda.xyz/mcp, sobre Streamable HTTP, sem estado (sem sessões). Toda chamada leva a chave como cabeçalho bearer, a mesma chave que você gerou acima.

Claude Code

Adicione o servidor uma única vez, a partir de um shell onde CORPUS_KEY esteja definida:

claude mcp add --transport http corpus https://corpus.trysenda.xyz/mcp \
  --header "Authorization: Bearer $CORPUS_KEY"

O conector MCP da Claude API

Passe o servidor como uma entrada mcp_servers na requisição, com a chave como o token de autorização dela. Confira a referência atual da API para os nomes exatos dos campos antes de colocar isso em produção; o conector ainda está evoluindo.

{
  "mcp_servers": [
    {
      "type": "url",
      "url": "https://corpus.trysenda.xyz/mcp",
      "name": "corpus",
      "authorization_token": "$CORPUS_KEY"
    }
  ]
}

Claude Desktop

A configuração de conectores remotos do Desktop (Settings, Connectors, adicionar um conector personalizado) espera que a maioria dos servidores entregue acesso via OAuth. O Corpus se autentica só por chave de API neste marco; a cadeia OAuth está planejada para um marco posterior. Se o fluxo de conectores do Desktop na sua versão não aceitar uma chave bearer diretamente, use o Claude Code ou o conector da API acima, que aceitam.

As ferramentas

Os nomes das ferramentas e seus schemas de entrada são gerados do mesmo documento OpenAPI que a API REST, então nunca se desalinham dela.

tool
list_expertsOs experts que a chave alcança, com sua versão publicada e o graphHash.
query_expertA que você mais vai usar: expert, goal e, opcionalmente, facts e question. O mesmo envelope de POST /v1/experts/{name}/query.
replay_decisionExecuta de novo uma decisão registrada por decision_id e reporta se a trace ainda bate.
search_documentsBusca ou lista os documentos no escopo da chave. Com query, busca de texto completo sobre as versões atuais, filtrada por type ou tag. Sem ela, os documentos publicados ordenados por título: title restringe por um trecho do título, limit define o tamanho da página (50 por padrão, 200 no máximo) e next no resultado é o cursor que você passa como cursor para a página seguinte, null na última. Cada resultado traz o id que get_document recebe.
list_tagsAs tags no escopo da chave: os nomes que create_document e ask_corpus recebem como tags.
get_documentUm documento, na versão atual dele ou numa versão que você indicar.
create_documentEscreve um documento novo. Cada tag que ele carrega precisa estar dentro do escopo da chave.
{
  "name": "query_expert",
  "arguments": {
    "expert": "publication-review",
    "goal": "reviewer_count",
    "facts": { "audience": "external", "contains_customer_data": "true" }
  }
}

O que needs_facts significa para o modelo

Quando query_expert volta com needs_facts, o modelo tem um único movimento correto: ler needs, pedir à pessoa exatamente aqueles concepts (nada mais, nada adivinhado), e chamar query_expert de novo com eles adicionados. Não há resposta parcial para dar nesse meio-tempo nem conclusão para inferir a partir dos fatos já conhecidos; esse é todo o sentido de ter um engine determinístico na frente do modelo. Um modelo que completa um fato faltante por conta própria, ou reporta uma conclusão à qual o engine nunca chegou, quebrou a única garantia que o Corpus existe para dar.


4. O contrato de recusa

Toda chamada a query_expert, via REST ou MCP, devolve um de quatro resultados. Ramifique sobre outcome, nunca sobre o texto de reason ou detail.

outcomesignificafaça isto
resolvedO engine chegou a uma conclusão. conclusion a contém; trace é a derivação completa.Use conclusion. Leia trace como evidência; não pergunte de novo.
needs_factsUma regra precisa de um fato que o engine não consegue derivar e que não foi fornecido. needs nomeia cada um, tipado.Consiga os fatos que faltam e chame de novo com eles adicionados.
out_of_scopeA meta não é um concept deste expert, ou a chave não tem escopo para alcançá-lo.Confira a meta e o escopo da chave. Não é um caso para tentar de novo.
errorA própria chamada foi rejeitada antes de a avaliação começar: autenticação inválida, corpo malformado, limite de taxa.Leia error.code e error.hint no corpo da resposta (seção 5), não este envelope.

5. Erros

Uma resposta de erro não leva o envelope da seção 4; é um corpo plano com um único objeto error: code, message, hint e traceId, o id a citar se você pedir ajuda.

{
  "error": {
    "code": "rate_limited",
    "message": "This key has exceeded its rate limit.",
    "hint": "Wait for the window in Retry-After, then retry the same request.",
    "traceId": "b3c1e9f2-1a2b-4c3d-8e9f-0a1b2c3d4e5f"
  }
}

Idempotency-Key

Toda escrita (criar um documento, uma versão nova, uma query, um replay) leva um cabeçalho Idempotency-Key. Envie a mesma chave com o mesmo corpo e você recebe o resultado original, não uma segunda escrita. Envie a mesma chave com um corpo diferente e a chamada falha com idempotency_key_reused: use uma chave nova por requisição distinta, e repita a mesma chave numa nova tentativa só quando ela for realmente idêntica.

codestatus HTTPsignificado
unauthenticated401Sem chave, ou uma chave que não pôde ser lida. Confira o cabeçalho Authorization.
revoked_key401A chave existiu mas foi revogada. Gere uma nova.
rate_limited429O limite de taxa da chave foi excedido. Retry-After indica a espera.
not_found404O recurso não existe, ou o escopo da chave não o alcança. Os dois casos parecem iguais de propósito.
validation_failed422O corpo da requisição não bate com o schema. hint diz qual campo.
idempotency_key_reused422O Idempotency-Key já foi enviado antes com um corpo diferente.
conflict409A escrita colidiu com o estado atual, por exemplo publicar sobre uma versão que já mudou.
quota_exceeded429A organização usou as decisões, respostas ou páginas indexadas do plano no período (seção 6). Retry-After diz quando a contagem reinicia.
internal500Algo falhou do lado do Corpus. Cite traceId se for reportar.

6. Planos e limites

Todos os planos têm todos os recursos; Basic, Pro e Enterprise mudam em quanto incluem de três coisas por mês. Não há plano grátis. O mês corre desde o início do seu período de cobrança.

Decisões
Uma chamada a query_expert, por REST ou MCP, com qualquer resultado. Ler, buscar e reproduzir não contam.
Respostas
Uma chamada a ask_corpus (ou POST /v1/answers) a cada duas partes da pergunta: uma pergunta que o Corpus divide em três subperguntas conta duas. É contada quando a pergunta é entendida e antes de qualquer busca. Uma chamada que termina em needs_facts conta uma decisão no lugar.
Páginas indexadas
3.000 caracteres do texto de uma versão publicada, arquivos anexos incluídos, arredondado para cima. Cada versão que você publica é indexada de novo, então conta de novo. Um documento longo custa o mesmo por página que um curto.
No limite
Passadas as decisões ou as respostas, a chamada retorna quota_exceeded (429) com Retry-After, e nada é registrado. Sem páginas, publicar retorna quota_exceeded e a versão fica como rascunho; uma versão mais longa que as páginas restantes não é indexada até você publicá-la de novo. O espaço de trabalho continua funcionando e avisa em 80% e em 100%.
Enterprise
Quantidades e preço combinados com cada cliente, com contrato anual. Escreva para nós pela página de preços.

As quantidades e os preços de cada plano