Service API

RDLLM includes a small reference HTTP service boundary for operators. It is intentionally small: run it behind the operator's normal reverse proxy, identity provider, TLS termination, and observability stack. The service also enforces a per-process protected-endpoint rate limit so a missing ingress rule does not turn attribution, metrics, or provider calls into an unbounded surface.

This process is not a multi-tenant identity gateway or distributed ledger. Its shared token, in-memory counters, and local JSONL audit log are suitable for a single operator boundary. Use external identity, tenant isolation, distributed rate limiting, and durable append-only storage for multi-instance or multi-tenant deployments, then verify those controls through the external attestation gate.

Start

Set the bearer-token hash. The service stores only the SHA-256 hash, not the raw token.

rdllm-service-config create \
  --template default \
  --corpus /srv/rdllm/corpus.json \
  --audit-log-path /var/lib/rdllm/audit/service.jsonl \
  --artifact-dir /srv/rdllm/artifacts \
  --output /etc/rdllm/service_config.json
rdllm-service-config validate --config /etc/rdllm/service_config.json
export RDLLM_SERVICE_TOKEN="replace-with-local-token"
export RDLLM_SERVICE_TOKEN_SHA256="$(python3 - <<'PY'
import hashlib
import os
print(hashlib.sha256(os.environ["RDLLM_SERVICE_TOKEN"].encode()).hexdigest())
PY
)"
rdllm-service --config /etc/rdllm/service_config.json

The service-config validation output validates against docs/schemas/service_config_verification.schema.json.

From a repository checkout, PYTHONPATH=src python3 tools/service_config.py provides the same create and validate commands, and PYTHONPATH=src python3 -m rdllm.service --config path/to/service_config.json starts the service. For real deployments, replace the local token, keep the raw token in a secret manager, and provide only the hash through RDLLM_SERVICE_TOKEN_SHA256.

Endpoints

GET /healthz

Liveness check. Does not require authentication.

GET /readyz

Readiness check. Does not require authentication. It verifies service config, auth hash availability, engine load, certification report, discovery manifest, provider attribution card, production-readiness report, production invocation admission, runtime conformance receipt, and source-grounded response receipt.

GET /.well-known/rdllm.json

Serves the configured discovery manifest. Does not require authentication.

GET /openapi.json

Serves a compact OpenAPI description. Does not require authentication.

POST /v1/attribute

Authenticated attribution endpoint. If output is supplied, RDLLM attributes the externally generated text. If output is omitted, RDLLM generates a deterministic demo answer from the registered corpus and returns the verified source footer.

Example:

curl -sS \
  -H "Authorization: Bearer ${RDLLM_SERVICE_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "What should royalty-bearing AI answers expose?",
    "output": "Every royalty bearing AI answer should have a provenance record. The record should include source identifiers, content hashes, retrieval scores, output citations, payout weights, and an event hash that allows auditors to replay the attribution.",
    "gross_revenue": "1.00"
  }' \
  http://127.0.0.1:8765/v1/attribute

Response shape:

The response is described by docs/schemas/service_attribution_response.schema.json.

Save a response and verify that the event, footer rows, rendered footer text, footer hash, and summary bindings still agree before showing or copying it:

rdllm-service-response-verify \
  --response response.json \
  --display-text copied-output.txt
rdllm-source-footer-verify \
  --footer source-footer.json \
  --display-text copied-output.txt

The verifier reports production_display_ready, which is true only when verification passes, the response status is ready, and grounding is safe for display. It also reports source_grounding_acceptance, which must be passed for any grounded or royalty-bearing user display. That acceptance profile checks source materialization, supported claim evidence, minimum support score, non-escrow royalty-share coverage, and answer-plus-footer display binding. A blocked response can still verify as internally consistent for audit, but launch approval requires production_display_ready: true, source_grounding_acceptance.status: passed, and response status: ready. The acceptance profile also reports answer_citation_marker_count, resolved_answer_citation_marker_count, citation_marker_status, answer_citation_markers, resolved_answer_citation_markers, and unresolved_answer_citation_markers; unresolved inline citation markers in the answer text fail production display unless they match verified source-footer source or claim labels. Numeric markers resolve as source aliases, so [1] and [Source: 1] are accepted only when source row S1 is verified. Numeric groups and ranges must fully resolve; four-digit bracketed years such as [2026] are ignored as non-citation text. The same profile reports answer_link_uris, resolved_answer_link_uris, unresolved_answer_link_uris, URI counts, and answer_link_status; Markdown links and bare scheme://... URIs in the answer fail production display unless they match verified source-footer source_uri values. Verification also checks footer semantics: source rows must match the supported claim rows they summarize, and supported claim evidence previews must be visible in the rendered claim-evidence footer and reproduce their declared evidence span hashes. Claim-evidence rows also expose claim_preview, claim_warrant_profile, claim_force_flags, evidence_force_flags, warrant_mismatch_flags, and warrant_strength_status; a ready response is rejected when related evidence does not carry the same relation, modality, scope, temporal, or numeric force as the displayed claim. They also expose source_disagreement_profile, agreement_source_labels, disagreement_source_labels, and source_disagreement_status; a ready response is rejected when a visible source plainly contradicts a supported claim. A ready response is also rejected if a visible source row omits or forges its verification handle. Rendered source rows expose uri, verify, and hash locators, plus support, text_match, weight, and payout. Here, payout is a candidate allocation amount, not evidence of an executed payment. Rows also expose usage_metric_profile, usage_metric_scope, and the method identifiers for each usage metric, so the copied/exported answer shows both where the source can be checked and which observable metric profile produced the allocation numbers. These are observable grounding and settlement-allocation metrics, not hidden model-internal reliance claims unless a provider supplies separately verified model-signal telemetry. source_grounding_acceptance reports source_usage_metric_names, source_usage_metric_row_count, source_usage_metric_status, source_usage_metric_profile, source_usage_metric_scope, source_usage_metric_methods, source_usage_metric_provenance_count, source_usage_metric_provenance_status, source_locator_count, source_locator_status, source_identity_count, and source_identity_status; grounded display fails if a visible source row omits required usage metrics, omits or changes the metric profile/scope/method disclosure, does not render its URI, verification handle, and content-hash prefix, or does not match the event source-reference metadata it claims to expose. It also reports temporal_claim_markers, source_temporal_metadata_count, and temporal_grounding_status; grounded display fails when temporal answer wording such as current, latest, recent, today, now, or as of appears without source freshness metadata on every visible source row. It also reports answer_claim_unit_count, answer_claim_row_coverage_count, uncovered_answer_claim_hashes, extra_claim_row_hashes, and answer_claim_coverage_status; grounded display fails if the answer text's claim units do not exactly match the footer claim rows. It also reports model_reliance_claim_markers, model_reliance_claim_marker_count, and model_reliance_claim_status; grounded display fails if the answer surface claims hidden model use, reasoning, or source influence beyond observable support/allocation fields. It also reports claim_evidence_row_count and claim_evidence_status; grounded display fails if any supported claim lacks a visible Claim Evidence row with a matching claim hash, support score, evidence span hash, character offsets, and evidence preview. It also reports claim_warrant_profile, claim_warrant_strength_count, and claim_warrant_strength_status; grounded display fails if any supported claim has over-strong wording relative to its evidence. It also reports source_disagreement_profile, claim_source_disagreement_count, and claim_source_disagreement_status; grounded display fails if a supported claim has visible high-overlap source disagreement. It also reports claim_source_closure_count and claim_source_closure_status; grounded display fails if a supported claim's source label, work ID, or chunk ID does not match the visible source row it is attributed to. It also reports attribution_gap_status, attribution_gap_verdict, accessed/credited source counts, and open-gap failure counts; grounded display fails unless the event's consumed, visible, and paid source coverage is closed. When --display-text is provided, the verifier also checks that the copied or exported text exactly matches display.rendered_text; stripping the footer or verification handles fails the report. For public handoff, rdllm-source-footer-verify validates a standalone source_footer artifact without requiring the private full response. It checks row hashes, claim evidence span hashes, deterministic verification handles, footer hash, event-id/event-hash consistency, footer-status/public-verifier consistency, and whether copied/exported text still contains the exact footer. The standalone footer report passes only for a verified, public-reliance-ready footer.

From a source checkout, use PYTHONPATH=src python3 tools/service_response_verify.py --response response.json. The verification outputs validate against docs/schemas/service_response_verification.schema.json and docs/schemas/service_source_footer_verification.schema.json.

POST /v1/provider/attribute

Authenticated provider-backed attribution endpoint. The service calls a configured OpenAI-compatible chat-completions provider route, captures the generated answer, hashes the provider response, and then runs RDLLM attribution before returning or releasing the answer.

Example payload:

{
  "provider_id": "openai-compatible-default",
  "messages": [
    {
      "role": "user",
      "content": "What should royalty-bearing AI answers expose?"
    }
  ],
  "gross_revenue": "1.00"
}

Provider routes are allowlisted in providers[] in the service config. Provider API keys must be supplied through the configured api_key_env; they are never stored in the config, response, audit log, or public proof artifacts.

Provider-backed responses use the same source_footer object. Clients should render source_footer.rendered_text or reconstruct their own UI from source_footer.source_rows and source_footer.claim_rows, then preserve source_footer.footer_hash in copied/exported output.

GET /v1/metrics

Authenticated JSON counters:

GET /v1/metrics/prometheus

Authenticated Prometheus-compatible text counters for the same service metrics.

Security Defaults

Smoke Test

Run:

PYTHONPATH=src python3 tools/service_smoke.py
PYTHONPATH=src python3 tools/service_load_smoke.py
PYTHONPATH=src python3 tools/provider_live_smoke.py
PYTHONPATH=src python3 tools/security_abuse_smoke.py

The smoke starts the real service, verifies liveness, readiness, security headers, unauthorized rejection, authenticated attribution, metrics, and hash-chained audit logging. The load smoke sends concurrent authenticated attribution requests and verifies metrics plus audit-chain continuity using the same verifier exposed as rdllm-service-audit-verify. The provider smoke starts a local OpenAI-compatible mock provider and verifies provider invocation, provider response hashing, attribution, metrics, and audit logging without requiring external API keys. The security abuse smoke verifies that unauthenticated calls, oversized prompts, negative revenue, unknown providers, missing provider keys, filtered provider responses, malformed provider responses, oversized provider output, and rate limits fail closed before a response can be treated as grounded.