Noetis API · v1

One request. A whole panel of specialists.

Send a decision and the material behind it. Encevra runs a panel of narrow specialists over that material in parallel — each confined to one job, each grounded in what the text actually says — then writes a single decision brief from what they reported. You get structured JSON your software can act on.

Real runs require approved trial or paid beta access. Follow the walkthrough before adding credits. Only released runs are charged at twice measured model cost; incomplete and failed runs are not charged.

Start and poll

POST returns a run id straight away; poll the run for the finished brief.

Six specialists in parallel

Terms, cost, risk, contradictions, gaps and questions — all at once.

Many model families

Frontier models for the heavy reasoning, faster hosted models for the narrow roles.

Quickstart

Everything you need is a key and one endpoint. Most teams integrate in an afternoon.

POST /api/public/v1/panel
curl https://encevra.com/api/public/v1/panel \
  -H "Authorization: Bearer enc_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "question": "Should we sign the Aster renewal, or move to Boreal?",
    "material": "ASTER SYSTEMS — RENEWAL PROPOSAL ... BOREAL PLATFORM — PROPOSAL ..."
  }'
202 Accepted
HTTP/1.1 202 Accepted
Retry-After: 10

{
  "object": "panel.run",
  "run_id": "a65dcf03-78e0-44a7-a99c-10231988d219",
  "status": "queued",
  "request_id": "9dd35145-...",
  "poll_url": "https://encevra.com/api/public/v1/runs/a65dcf03-...",
  "retry_after_seconds": 10,
  "estimated_duration_seconds": 90,
  "quota": {
    "runs_this_month": 12,
    "monthly_run_limit": 100,
    "rate_limit_per_minute": 6
  }
}

A panel takes 60 to 120 seconds, so the request does not wait for it. You get a run id and a poll_url immediately, then read the finished brief from that url. If you would rather hold the connection open, send "mode": "sync" and set a client timeout of at least 300 seconds.

Choosing an engine

Send an optional engine field with each request. Leave it out to get the full Noetis panel.

Request body field
"engine": "noetis"   // default: seven specialists, up to 150,000 characters
"engine": "lite"     // three specialists, faster, up to 40,000 characters
"engine": "legal"    // any catalog template slug, see GET /api/public/v1/engines
"engine": "custom:<id>", "engine_version": 2   // your Engine Studio engine, pinned
"end_user": { "id": "u_123", "context": { "tone": "plain" } }   // optional, preferences only
Related endpoints
GET    /api/public/v1/engines          // catalog: ids, versions, inputs, roles, tool availability
DELETE /api/public/v1/runs/<id>        // cancel a queued run (credit hold released, never charged)
PUT    /api/public/v1/end-users/<id>   // store end user preferences, scoped to your account
GET    /api/public/v1/end-users/<id>   // inspect them
DELETE /api/public/v1/end-users/<id>   // delete them
Minimal wrapper (Node 18+)
const r = await fetch("https://encevra.com/api/public/v1/panel", {
  method: "POST",
  headers: { Authorization: "Bearer " + process.env.ENCEVRA_KEY, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({ engine: "procurement", mode: "async", question, material, webhook_url }),
});
const { run_id, poll_url } = await r.json();
// poll poll_url (or wait for the signed webhook) until status is released, incomplete or failed

Lite runs the same evidence location, claim review, synthesis and release check with fewer specialists and faster models. In our internal timing it returned in about 23 to 28 seconds. The response echoes the engine that ran. Read how Lite works or compare it with Noetis.

Polling a run

The same key that started a run is the only key that can read it.

GET /api/public/v1/runs/:id
curl https://encevra.com/api/public/v1/runs/a65dcf03-... \
  -H "Authorization: Bearer enc_live_..."

# while the panel is working
{ "status": "running", "retry_after_seconds": 10, ... }

# once it is done, the full panel.run object below

While a run is queued or running you get its status, question, size and retry_after_seconds. Once it succeeds, the same url returns the full panel.run object below, including token counts and the cost of the run. Poll no faster than the interval we return.

Completion webhooks

Optional. Add webhook_url (https, public host) to the request body and we POST the finished run to it, so you do not need to poll.

Encevra-Signature header
Encevra-Signature: t=1760000000,v1=<hex HMAC-SHA256>

signed_payload = t + "." + raw_request_body
expected = HMAC_SHA256(your_webhook_secret, signed_payload)

The event type is panel.run.completed or panel.run.failed, and the body carries the same brief as the polling response. Compare signatures in constant time and reject timestamps older than five minutes. A delivery that does not get a 2xx response is retried up to three times; polling keeps working either way. Your webhook signing secret is issued with your API key.

Email in

Forward a contract to your Encevra address and the brief appears in the portal inbox. No integration work for the people doing the forwarding.

POST /api/public/v1/email-in
POST /api/public/v1/email-in
x-encevra-email-secret: <your intake secret>

{
  "from": "buyer@yourcompany.com",
  "to": "analyse@in.encevra.com",
  "subject": "Aster renewal - please review",
  "text": "plain-text body of the forwarded email",
  "attachment_text": "extracted text of the attached contract",
  "attachment_name": "aster-renewal.pdf",
  "question": "optional override; otherwise built from the subject"
}

The sender address must match an Encevra account; the run is filed against that account and shows up under Email inbox in the portal. Mail from an unknown address is logged and ignored rather than run. Attachment text is preferred over the message body, and quoted replies and signatures are stripped before analysis.

Authentication

Every request carries a customer key as a bearer token. Keys are stored hashed — we cannot recover one for you, so save it when it is issued.

Authorization: Bearer enc_live_...

Keys are server-side credentials. Never ship one in browser code or a mobile app; call Encevra from your backend.

Models by engine

You choose an engine with the request's engine field. The models follow from that choice; there is no per request provider parameter.

Model providers we build with

  • OpenAI
  • Anthropic
  • SpaceXSI
  • Meta Superintelligence Labs
  • Google DeepMind
  • Moonshot AI
  • NVIDIA
  • Cerebras
  • Hugging Face
  • TypeSafe
EngineRequestModel providers
Full Noetisomit engineOpenAI (GPT), Anthropic (Claude), Google DeepMind (Gemini)
Noetis Lite"engine": "lite"OpenAI (GPT), Google DeepMind (Gemini)
Catalog engines"engine": "<slug>"OpenAI (GPT), Anthropic (Claude)
Studio engines"engine": "custom:<id>"Same as catalog engines

Transient provider failures can fall back to an OpenAI model; the run's trace records the model that actually answered. Executed roles and usage by model are in the response.

Request body

JSON, two fields.

FieldTypeDescription
questionstring, 3–500 charsThe decision you want the panel to weigh in on.
materialstring, 40 to 150,000 charsThe source text. Contracts, transcripts, reports — plain text, concatenated is fine.

Response

A panel.run object: one entry per specialist, plus the synthesized brief and your usage.

200 OK
{
  "run_id": "3d4fbb61-77a2-452b-8fe9-f7bbe706c23b",
  "request_id": "0f2a...",
  "object": "panel.run",
  "question": "Should we sign the Aster renewal, or move to Boreal?",
  "specialists": [
    {
      "id": "terms",
      "name": "Terms reader",
      "focus": "Obligations, durations, renewal and termination language...",
      "model": "Claude Fable 5.1",
      "status": "ok",
      "findings": [
        {
          "label": "Auto-renewing 24-month term",
          "statement": "The term renews for successive 12-month periods unless cancelled 90 days ahead.",
          "citation": {
            "quote": "auto-renewing for successive 12-month periods unless cancelled 90 days",
            "status": "verified",
            "start": 118,
            "end": 188
          }
        }
      ],
      "citations": { "findings": 3, "verified": 3, "unverified": 0, "rate": 1 },
      "report": "Auto-renewing 24-month term: ...",
      "error": null
    }
    // ...one entry per specialist
  ],
  "brief": {
    "text": "Bottom line: ...\nKey findings:\n- ...\nOpen questions:\n- ...",
    "model": "Claude Fable 5.1",
    "built_from": "verified findings only"
  },
  "citations": { "findings": 21, "verified": 20, "unverified": 1, "rate": 0.95 },
  "models_used": ["Claude Fable 5.1", "GPT-6 Astra", "Claude Opus 5.5"],
  "usage": {
    "input_tokens": 15675,
    "output_tokens": 10458,
    "cost_usd": 0.281,
    "estimated": false,
    "by_model": [
      { "model": "claude-fable-5-1", "calls": 7, "inputTokens": 12667, "outputTokens": 7963, "costUsd": 0.2624 },
      { "model": "gpt-6-astra", "calls": 2, "inputTokens": 1188, "outputTokens": 366, "costUsd": 0.0133 }
    ]
  },
  "quota": {
    "runs_this_month": 12,
    "monthly_run_limit": 100,
    "rate_limit_per_minute": 6
  },
  "duration_ms": 58213
}

Terminal checks and release risk

release_risk_score and release_risk_confidence_interval are currently null. release_risk.status is uncalibrated and autoReleaseEligible is false. Structural completion is not a calibrated correctness probability.

revision_drift reports outstanding terminal claim-check issues, including missing supported risks and contradictions. It is null when no terminal check is available. revision_drift_details retains initial issues and whether the bounded repair resolved them.

Noetis uses conservative role triage, with the full panel retained for missing, invalid, ambiguous, or high-risk routing. role_triage records selected, skipped, and executed roles. Evidence review, checked synthesis, and the release gate still run; no cost or latency improvement is claimed.

A specialist that fails does not fail the run: its entry carries status: "failed" and an error, and the brief is written from the rest.

Verification states and deployment policy

Source validation and interpretation review are reported separately. Build your own evidence viewer from the quote, start and end span returned on each finding.

  • Source verified: every cited passage was located in the material for that run. Spans are half-open character offsets into that exact text and are never re-based onto a later upload.
  • Insufficient evidence: a quote could not be located. The finding is not presented as a verified conclusion.
  • Interpretation requires review: the passage exists, but whether it supports the claim still needs a reviewer.
  • Conflicting evidence: the finding reports passages that disagree.
  • Extraction incomplete: some source text could not be read, so absence of a finding is not absence of risk.

Deployment policy: every specialist call, retry and fallback is checked server side against approved models. A blocked call ends the attempt with an error; it is never rerouted to another provider. Hosted is the only deployment mode available today. Citations returned by the API reference text in the request body; Encevra does not issue public document URLs.

Verified citations and claim support

Every finding carries a type, an identifier, and the support that type requires. Location and support are checked separately, because a real quotation can accompany an incorrect interpretation.

citation.status: verified

The quote was located in your material. start and end are character offsets into the exact string you sent, so you can highlight it directly.

citation.status: unverified

The quote could not be located. The finding is returned, flagged, with a reason, and it is withheld from the decision brief.

citation.status: not_required

The finding is a bounded gap or a question. It is scoped to the submitted material and is never asked to quote an absence.

type

source, calculation, inference, contradiction, gap, question or recommendation. Each type states its own support requirement inside the statement.

claim_support.status

supported, narrowed, unsupported or unreviewed, decided after the analysis pass by a review over the completed findings and their surrounding source context. A narrowed finding returns the narrowed statement, with the original in original_statement.

brief.final_check

The finished brief is checked back against the approved findings. issues lists any claim that went beyond them.

execution_trace

Per role: requested_model, actual_model, fell_back, fallback_reason and latency_ms. A substitute model passes the same checks, and models_used reflects what actually ran.

citations

Per specialist and per run: findings, verified, unverified, notRequired, rate, supported, narrowed and unsupported, so you can gate on evidence rather than prose.

Matching ignores whitespace, letter case, curly quotes and dash style, and supports quotes joined by an ellipsis. It does not accept paraphrase. A claim with no locatable passage behind it, or one the review pass rejects, cannot reach the brief. If no finding survives both checks, the run fails rather than returning an ungrounded brief, and brief.decisive_evidence tells you whether any fully supported finding was available at all.

Running it in production

What to expect when this sits behind your own product.

GET /api/public/v1/health
{ "status": "ok", "service": "encevra-panel-api", "version": "1", "specialists": 8 }

Rate limits

6 runs per minute per key, plus a monthly run limit. Both are reported in usage on every successful run.

Request ids

Send X-Request-Id and we echo it back in the header and body; otherwise we mint one. Quote it in support.

Every run recorded

Question, size, status, duration, models and citation results are stored per key for reconciliation.

Retries

Runs are not idempotent, so retry only on 429 with Retry-After, or on 502 after inspecting the message.

The panel

Each specialist reads the same material at the same time, confined to one focus. That confinement is the point — nobody averages away what they found.

terms

Terms reader

Obligations, durations, renewal and termination language, exactly as written.

cost

Cost analyst

Money: figures, totals, escalators. Rebuild arithmetic instead of trusting a stated total.

risk

Risk specialist

Exposure: liability, indemnity, penalties, one-sided remedies.

contradiction

Contradiction detector

Statements in the material that cannot both be true, and internal inconsistencies.

gaps

Gap finder

What is missing: the term nobody specified, the number nobody provided.

questions

Question generator

The specific questions worth asking the other side before deciding.

causal

Causal reviewer

Cause-and-effect claims: the assumptions they need, plausible confounders, and what the material cannot establish.

verify

Evidence verifier

Runs after the analysis pass, over the completed findings: whether each located passage supports the whole claim, and whether scope, conditions and exceptions were respected.

Errors

Failures return { "error": { "code", "message" } } with a matching HTTP status.

StatusCodeMeaning
401missing_api_keyNo Authorization header, or it is not a bearer token.
401invalid_api_keyThe key is unknown or has been deactivated.
400invalid_requestBody failed validation — the message names the field.
429rate_limitedMore than 6 runs started in a minute on this key. Retry-After is set.
429quota_exceededMonthly run limit reached; resets at the start of next month.
502panel_failedEvery specialist failed, or nothing they produced could be traced to the source.

Limits and usage

Each key carries a monthly run limit. Every response reports where you stand.

"quota": { "runs_this_month": 12, "monthly_run_limit": 100 }
"usage": { "input_tokens": 15675, "output_tokens": 10458, "cost_usd": 0.281, "estimated": false }

Every finished run reports the tokens each model consumed and what the run cost, broken down per model in usage.by_model. A short contract runs around $0.30; a full 150,000-character document costs more. estimated: true means at least one provider did not report counts and we measured the text instead.

Material is capped at 150,000 characters per run on the full Noetis engine, or 40,000 on the Lite engine. For longer corpora, split by document and run a panel per document, or talk to us about higher limits.

Examples

Compact, defensive Node/TypeScript: bounded total timeout, bounded retries with backoff on 429 and 5xx, and a stop on every terminal status.

Polling (Node.js)
// Poll a run started by POST /api/public/v1/panel.
// Reads the key from an environment variable; never hardcode it.
const apiKey = process.env.ENCEVRA_API_KEY; // required: enc_live_... or enc_test_...
if (!apiKey) throw new Error("Set ENCEVRA_API_KEY in your environment.");
const auth = { Authorization: `Bearer ${apiKey}` };

const TERMINAL = new Set(["released", "incomplete", "failed"]);
const TOTAL_TIMEOUT_MS = 10 * 60 * 1000; // give up after 10 minutes total
const MAX_RETRIES = 6; // bounded retries for 429 / 5xx, not for terminal states

async function pollRun(pollUrl) {
  const deadline = Date.now() + TOTAL_TIMEOUT_MS;
  let retryAfterMs = 10_000;
  let attempt = 0;

  while (Date.now() < deadline) {
    const res = await fetch(pollUrl, { headers: auth });

    if (res.status === 429 || res.status >= 500) {
      attempt += 1;
      if (attempt > MAX_RETRIES) throw new Error(`Polling failed after ${MAX_RETRIES} retries (last status ${res.status}).`);
      const retryAfterHeader = Number(res.headers.get("retry-after"));
      const backoffMs = Number.isFinite(retryAfterHeader) && retryAfterHeader > 0
        ? retryAfterHeader * 1000
        : Math.min(60_000, 1000 * 2 ** attempt);
      await sleep(backoffMs);
      continue;
    }

    if (!res.ok) {
      const body = await res.json().catch(() => ({}));
      throw new Error(`${body?.error?.code ?? res.status}: ${body?.error?.message ?? "request failed"}`);
    }

    const run = await res.json();
    attempt = 0; // reset backoff after a healthy response

    if (TERMINAL.has(run.status)) {
      if (run.status === "failed") throw new Error(run.error?.message ?? "The run failed.");
      return run; // "released" or "incomplete": read run.brief.text
    }

    retryAfterMs = (run.retry_after_seconds ?? 10) * 1000;
    await sleep(retryAfterMs);
  }

  throw new Error("Timed out waiting for the run to finish.");
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
Verifying a webhook (Node.js)
// Verify a completion webhook POST. Reads the signing secret from an
// environment variable; it is issued alongside your API key.
// Header format: Encevra-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256>
// signed_payload = "<t>.<raw request body>"
import { createHmac, timingSafeEqual } from "node:crypto";

const TOLERANCE_SECONDS = 300;

export function verifyEncevraWebhook(header, rawBody, secret) {
  if (!header) return { ok: false, reason: "missing_header" };

  const parts = Object.fromEntries(
    header.split(",").map((p) => p.trim().split("=")).filter((kv) => kv.length === 2),
  );
  const t = parts.t;
  const v1 = parts.v1;
  if (!t || !v1 || !/^d+$/.test(t) || !/^[0-9a-f]{64}$/i.test(v1)) {
    return { ok: false, reason: "malformed_header" };
  }

  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(t)) > TOLERANCE_SECONDS) {
    return { ok: false, reason: "stale_timestamp" };
  }

  const secretEnv = process.env.ENCEVRA_WEBHOOK_SECRET || secret;
  if (!secretEnv) return { ok: false, reason: "missing_secret" };

  let expected;
  try {
    expected = createHmac("sha256", secretEnv).update(`${t}.${rawBody}`).digest("hex");
  } catch {
    return { ok: false, reason: "bad_signature" };
  }

  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(v1, "hex");
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return { ok: false, reason: "bad_signature" };
  }
  return { ok: true };
}

// Example handler:
// app.post("/webhooks/encevra", express.raw({ type: "application/json" }), (req, res) => {
//   const result = verifyEncevraWebhook(req.header("Encevra-Signature"), req.body.toString("utf8"), process.env.ENCEVRA_WEBHOOK_SECRET);
//   if (!result.ok) return res.status(401).send("invalid signature");
//   const event = JSON.parse(req.body.toString("utf8"));
//   // event.type is "panel.run.completed" or "panel.run.failed"
//   res.status(200).send("ok");
// });
Python (illustrative, add your own retry and timeout bounds)
import os, time, requests

auth = {"Authorization": f"Bearer {os.environ['ENCEVRA_API_KEY']}"}

start = requests.post(
    "https://encevra.com/api/public/v1/panel",
    headers=auth,
    json={"question": question, "material": material},
    timeout=30,
)
start.raise_for_status()
job = start.json()

while True:
    time.sleep(job.get("retry_after_seconds", 10))
    run = requests.get(job["poll_url"], headers=auth, timeout=30).json()
    if run["status"] in ("released", "incomplete"):
        print(run["status"], run["brief"]["text"])
        break
    if run["status"] == "failed":
        raise RuntimeError(run["error"]["message"])

Access, status and spending

Noetis is in supervised trials. Every key carries one of these labels.

  • Simulated Test mode. Returns a fixed, labelled sample brief and never calls a model.
  • Supervised trial Real runs for approved accounts, within a fixed allowance set by Encevra.
  • Paid beta Live billing by approval, at the self serve rate.
  • Admin issued Keys issued directly by Encevra under a negotiated agreement.

Self serve pricing is the published per run rate. Negotiated pricing applies to contracts arranged with us. Model provider cost is our cost and is never passed through line by line; you pay the customer price for released runs only.

Run statuses
Every surface uses the same five words. queued and running mean work is in progress. released means the brief passed the release checks. incomplete means the run finished but the brief did not pass, so read it as a draft. failed means the run could not finish.
Charging
Only released runs are charged. Incomplete and failed runs cost you nothing; the credit held for them is returned.
Polling
Async requests return a run id at once. Poll GET /api/public/v1/runs/{id} every few seconds until the status is released, incomplete or failed.
Webhooks
Pass webhook_url to receive one signed POST when the run reaches a final status. Verify the Encevra-Signature header (t= and v1=, an HMAC SHA-256 of "t.body" with your key's webhook secret) and reject anything older than five minutes.
Idempotency
Send an Idempotency-Key header to retry safely. The same key with the same body returns the original run. The same key with a different body returns 409 idempotency_conflict.
Spending limits
Each run holds a fixed amount of credit before it starts, across every key on your account, so parallel requests can never overspend. A request that cannot be covered returns 402 with the hold amount it needs.
Recovery
If a request times out on your side, retry with the same Idempotency-Key instead of sending a new one. Runs abandoned by a worker are retried a limited number of times, then marked failed and their hold is released.
Material limit
Up to 150,000 characters of source text per run. Larger material is rejected with 400 rather than silently cut.

Minimal working example

curl https://encevra.com/api/public/v1/panel \
  -H "Authorization: Bearer $ENCEVRA_KEY" \
  -H "Idempotency-Key: renewal-2026-10-001" \
  -H "Content-Type: application/json" \
  -d '{"mode":"async","question":"Which renewal should we accept?","material":"--- Source: Proposal A ---\n..."}'

# then
curl https://encevra.com/api/public/v1/runs/RUN_ID -H "Authorization: Bearer $ENCEVRA_KEY"

Estimate your price

Tell us the use case and the size of the job. The estimate rebuilds the panel's real call graph at list token prices, so the number tracks what a run actually costs us — then we confirm a per-run rate with you.

Estimated cost per run

$0.262

11 model calls, ~106s

Monthly compute

$52.41

200 runs at that size

Indicative monthly price

$104.83

Compute x 2 service margin

The estimate rebuilds the panel's actual call graph — seven specialists, claim review, synthesis and the final check — at list token prices for the models each role runs on. The indicative price is a starting point for the conversation, not a published rate; volume commitments change it.

Your estimate is sent with the request so we can confirm the rate. No spam, no newsletter.

Getting a key

The API is in limited access while we expand capacity.

Machine-readable spec

The same endpoints, as an OpenAPI 3.1 document, for generating clients and importing into API tools.

GET /api/public/openapi.json
https://encevra.com/api/public/openapi.json

Noetis engines and Engine Studio

36 configurable engines on one runtime

24 business and 12 personal templates, each a configured set of specialist responsibilities, tools and rules on the shared Noetis runtime. Start from a template or describe your own engine in Engine Studio. None has a published scored evaluation yet. Live API use requires an approved account, and customer facing use requires a licensing agreement.

Implemented: 36