Trust API

Rasepi is a trust-control plane for company knowledge. The Trust API answers one question before your AI, your agents, or your pipelines rely on a document: can this knowledge be safely used right now, by this actor, for this purpose?

Overview

The Trust API is the machine-facing surface of Rasepi. External systems — chatbots, RAG pipelines, agents, CI gates — call it before using content. It evaluates each source against freshness, review state, ownership, provenance, policy, and the context of the request, then returns a decision per source:

allowwarnblockescalate

Every evaluation is recorded in the decision ledger with the full per-source evidence, so you can always answer: who or what relied on which knowledge, and why was it allowed?

Decisions, not just scores

Each source gets a decision with machine-readable reasons (code, dimension, severity) and obligations like citation or disclaimer requirements.

Context-aware

The same document can be allowed for an internal human and blocked for a customer-facing bot. Actor, action, and channel are part of the evaluation.

Not a proxy

Rasepi never sees your prompts or your model traffic. You call it with candidate sources; it returns decisions. Your AI stack stays yours.

Audited by default

Every call creates a ledger record with a decisionId you can fetch later for audits and debugging.

Authentication

The Trust API is built for machines. Two authentication methods are supported:

API keys

Create a tenant API key in the admin panel, or — recommended — register an AI application and mint a key bound to it (see AI App Registry) so every decision is attributed to a named, owned system. Pass the key as a Bearer token or via the X-Api-Key header:

Authorization: Bearer rk_live_xxxxxxxxxxxx
# or
X-Api-Key: rk_live_xxxxxxxxxxxx

Client credentials

For OAuth-based setups, use the client-credentials flow to obtain a machine token, then send it as a Bearer token.

Scopes

ScopeGrants
trust:evaluatePOST /api/trust/evaluate, POST /api/trust/filter-sources
trust:readGET /api/trust/sources, GET /api/trust/decisions
trust:usagePOST /api/trust/usage

All calls are scoped to the tenant of the key or token. Signed-in tenant users can call the same endpoints with their session for testing.

Source IDs

Every endpoint accepts source identifiers in three forms:

FormExampleResolves against
entry:{guid}entry:7f3a…c91dA native Rasepi entry
{connectorKey}:{externalId}confluence:SPACE:12345A connected external document
http(s) URLhttps://wiki.acme.com/x/Ab12Connector URL, then external source links

Unresolvable IDs do not fail the request. They come back marked unresolved, so a typo in one source never blocks the rest of the batch.

Decision Model

Each evaluated source returns this structure:

{
  "sourceId": "confluence:SPACE:12345",
  "entryId": "7f3a…c91d",
  "title": "OAuth Migration Guide",
  "origin": "Connected",
  "decision": "block",
  "reasons": [
    {
      "code": "source_changed_after_review",
      "dimension": "freshness",
      "severity": "block",
      "message": "Linked API reference changed after the last approved review.",
      "data": { "sourceChangedAt": "2026-06-10T08:14:00Z" }
    }
  ],
  "freshnessScore": 41,
  "classification": "Published",
  "policySource": "tenant-default",
  "recommendedAction": "request_review",
  "requirements": {
    "citationRequired": true,
    "disclaimerRequired": false,
    "disclaimerText": null
  }
}

Reasons are machine-readable: code identifies the rule, dimension names the trust dimension it came from (freshness, ownership, provenance, policy, sensitivity, context), and severity is the decision level that reason alone would justify. requirements carries obligations your application must honor when it uses a warned source.

POST /api/trust/evaluate

Evaluate up to 100 sources for a given actor, action, and channel. Use this when you want the full per-source verdict in one flat list.

POST /api/trust/evaluate
Authorization: Bearer rk_live_xxxxxxxxxxxx
Content-Type: application/json

{
  "sources": ["confluence:SPACE:12345", "entry:7f3a…c91d"],
  "context": {
    "actorType": "ai_agent",
    "actorLabel": "support-bot",
    "action": "answer_customer",
    "channel": "public_support_chat",
    "externalModel": true
  }
}

Response:

{
  "decisionId": "0d9f…4b21",
  "evaluatedAt": "2026-06-12T09:30:00Z",
  "overallDecision": "block",
  "results": [ /* one decision object per source, see Decision Model */ ]
}

POST /api/trust/filter-sources

The RAG-shaped variant of evaluate. Same request body, but the response is partitioned so you can pass allowed (and optionally warned, honoring their obligations) straight to your LLM context:

{
  "decisionId": "0d9f…4b21",
  "evaluatedAt": "2026-06-12T09:30:00Z",
  "allowed": [ /* safe to use */ ],
  "warned":  [ /* usable with obligations: citations, disclaimers */ ],
  "blocked": [ /* must not enter the prompt (includes escalations) */ ]
}

This is the recommended default integration: retrieve candidates first, filter them, then generate.

GET /api/trust/sources/{sourceId}

Decision-free trust metadata for one source — for UI badges, browser extensions, and dashboards. For slash-heavy IDs use the query form ?sourceId=….

GET /api/trust/sources?sourceId=confluence:SPACE:12345

{
  "sourceId": "confluence:SPACE:12345",
  "resolved": true,
  "entryId": "7f3a…c91d",
  "title": "OAuth Migration Guide",
  "origin": "Connected",
  "status": "Published",
  "version": 12,
  "externalUrl": "https://wiki.acme.com/x/Ab12",
  "contentUpdatedAt": "2026-06-10T08:14:00Z",
  "policy": {
    "source": "tenant-default",
    "aiRetrievable": true,
    "customerFacingAllowed": false,
    "internalOnly": false,
    "externalModelAllowed": true,
    "maxSensitivityForAi": "Internal"
  }
}

POST /api/trust/usage

Log that an AI system actually relied on certain sources, optionally linked to an earlier decision. Closes the loop between "allowed" and "used".

POST /api/trust/usage

{
  "decisionId": "0d9f…4b21",
  "actorLabel": "support-bot",
  "action": "answer_customer",
  "sources": ["confluence:SPACE:12345"],
  "outcome": "answer_sent"
}

// 202 Accepted
{ "usageId": "b7e2…91aa" }

GET /api/trust/decisions/{decisionId}

Explain a past decision: the full ledger record with per-source evidence as it was at decision time — freshness score, classification, policy source, and reasons. This is your audit trail.

{
  "decisionId": "0d9f…4b21",
  "evaluatedAt": "2026-06-12T09:30:00Z",
  "overallDecision": "block",
  "actorKind": "machine",
  "actorLabel": "support-bot",
  "action": "answer_customer",
  "channel": "public_support_chat",
  "endpoint": "FilterSources",
  "counts": { "total": 12, "allowed": 7, "warned": 2, "blocked": 3, "escalated": 0 },
  "sources": [ /* per-source state at decision time */ ]
}

Pattern: RAG Gate

Gate sources after retrieval and before generation. Your retriever stays untouched; you add one call:

const candidates = await retriever.search(question)   // your existing retrieval

const res = await fetch("https://api.rasepi.com/api/trust/filter-sources", {
  method: "POST",
  headers: { "X-Api-Key": process.env.RASEPI_API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    sources: candidates.map(c => c.url),
    context: { actorType: "ai_agent", actorLabel: "support-bot",
               action: "answer_customer", channel: "public_support_chat" }
  })
})
const { allowed, warned, decisionId } = await res.json()

const context = [...allowed, ...warned]               // honor warned[].requirements
const answer = await llm.generate(question, context)  // your existing generation

See the Getting Started guide for the full walkthrough including usage logging.

Pattern: CI/CD Docs Gate

Block a release when the docs it depends on are stale. Evaluate the relevant sources in your pipeline and fail on block:

DECISION=$(curl -s https://api.rasepi.com/api/trust/evaluate \
  -H "X-Api-Key: $RASEPI_API_KEY" -H "Content-Type: application/json" \
  -d '{"sources":["entry:7f3a…c91d"],"context":{"actorType":"ci","actorLabel":"release-pipeline","action":"ship_release","channel":"internal"}}' \
  | jq -r .overallDecision)

if [ "$DECISION" = "block" ]; then
  echo "Release blocked: public docs are not trustworthy. See the decision ledger."
  exit 1
fi

Pattern: Trust Badges

Show trust status inside the tools where people already work. GET /api/trust/sources is cheap, decision-free, and made for badges in browser extensions, portals, and dashboards. Render the policy flags and freshness state next to the document title, with a link back to Rasepi for the evidence.

AI App Registry

Register each AI system that calls the Trust API as a named, owned application. Every decision a bound key makes is then attributed to that app — its owner, channel, and declared risk — and an app can carry its own default policy. These are tenant-admin endpoints (session or admin auth), not part of the machine key surface.

MethodPathPurpose
GET/trust-admin/appsList registered apps
POST/trust-admin/appsRegister an app (name, channel, risk, default policy)
POST/trust-admin/apps/{id}/keysMint a key bound to the app (an expiry is required)
POST/trust-admin/apps/{id}/keys/{keyId}/regenerateRotate a key (revoke + reissue)
POST/trust-admin/apps/{id}/keys/{keyId}/disableReversibly disable a key (or /enable)
GET/trust-admin/apps/{id}/statsPer-app decision counts and per-key request volume

App keys are first-class credentials: they must carry an expiry, can be disabled and re-enabled without losing history, and rotate in place. The decisionId on every evaluation resolves back to the app that made it.

Compliance Reports

The decision ledger, freshness engine, and registry already hold the evidence; the report endpoints aggregate it. Each returns the same tabular shape (explicit columns plus rows), and every report exports to CSV or JSON for evidence handoff. All accept a date window and an optional appId filter.

GET /trust-admin/reports                       # list report keys
GET /trust-admin/reports/{key}?from=&to=&appId=
GET /trust-admin/reports/{key}/export?format=csv
Report keyAnswers
docs-blockedWhat was blocked from AI use, by app and reason code
customer-facing-readinessAllowed vs warned vs blocked for customer-facing apps
expired-knowledge-usedStale knowledge that AI actually relied on
unreviewed-accessibleAI-accessible docs that were never reviewed
used-without-ownerAI-used docs with no (or an inactive) owner
policy-overridesActive per-entry policy overrides
classification-summaryDocument counts by sensitivity classification

PII & Secrets Scanning

When auto-scanning is enabled for a tenant, Rasepi scans document text during connector sync and on native save for personal data (emails, phone numbers, payment-like numbers) and secrets (API keys, tokens, private keys). Detections set the entry's sensitivity facts automatically, which the sensitivity dimension then weighs at evaluation time — so a synced page carrying an API key is flagged and can be blocked for customer-facing AI without anyone touching it.

The scanner never overrides a human-set fact, records every detection for the classification report, and content is scanned in-flight, not stored. No API change is needed on your side: the same filter-sources call simply starts returning block for sources that carry secrets.

Runnable example

A complete, standalone external RAG bot that runs its own retrieval and model calls and gates every answer through the Trust API lives in demo/external-rag. It seeds a handbook into Rasepi, registers an app and mints a key, then shows allow / warn / block live — including the contrast of running the same bot with the gate switched off.

cd demo/external-rag
pip install -r requirements.txt
python seed.py        # registers the app, mints a key, seeds docs
python ingest.py      # builds the local vector index
python ask.py "what are our supplier margins?"   # -> blocked (internal only)

See its README.md for the full walkthrough and the "gate off" contrast.