🚀 Beta: All Pro and Team features are free. Install on GitHub →

Overview Authentication Rate Limits Endpoint GraphQL Diff gRPC Diff AsyncAPI Diff Public Preflight Public ActionGuard Agent Abort Instability (AIDE) Metrics Dashboard Crawler Stats Attestation Evidence API Keys Headers & Debug Response Try It Live Errors Examples Templates

Overview

The CodeRifts API detects breaking changes between two OpenAPI specifications. Send your old and new specs, get back a full governance report with risk scoring, breaking changes, security findings, and policy violations.

Use it in CI/CD pipelines, local development workflows, or any system that needs to validate API contract changes before deployment.

https://app.coderifts.com/api/v1

Authentication

Include your API key in the Authorization header as a Bearer token:

HTTP Header
Authorization: Bearer cr_live_your_key_here

Don't have a key? Get one free → No credit card required. Free tier includes 1,000 requests per month.

Rate Limits

TierMonthly RequestsPer MinutePrice
Free1,000100$0
ProUnlimited100$49/mo

Rate limit information is returned in response headers:

HeaderDescription
X-RateLimit-Remaining-MonthlyRequests remaining this month
X-RateLimit-Limit-MonthlyTotal monthly request limit
X-RateLimit-Remaining-MinuteRequests remaining this minute

Endpoint: POST /v1/diff

Analyze the difference between two OpenAPI specifications and return a full governance report.

Request

cURL
curl -X POST \
  https://app.coderifts.com/api/v1/diff \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "old_spec": "openapi: 3.0.0\ninfo:\n  title: My API\n  version: 1.0.0\npaths:\n  /users:\n    get:\n      summary: List users\n      responses:\n        \"200\":\n          description: OK",
    "new_spec": "openapi: 3.0.0\ninfo:\n  title: My API\n  version: 2.0.0\npaths: {}"
  }'

Request Body

FieldTypeRequiredDescription
old_spec string required The base (old) OpenAPI specification as a YAML or JSON string
new_spec string required The head (new) OpenAPI specification as a YAML or JSON string
config object optional Configuration options (see below)

Config Options

FieldTypeDefaultDescription
format string "json" Response format: "json" or "markdown"
risk_threshold number 50 Risk score threshold (0-100) for should_block
block_on array [] Change types that should trigger blocking (e.g., ["removed-endpoint", "auth-removed"])

Response

JSON
{
  "decision": "BLOCK",
  "safe_for_agent": false,
  "risk_score": 60,
  "breaking_changes": 4,
  "requires_migration": true,
  "evidence_quality": "MEDIUM",
  "patterns": ["AUTH_SCHEME_REMOVAL", "TYPE_NARROWING", "ENUM_NARROWING", "ENDPOINT_REMOVAL"],
  "confidence_score": 48,
  "coderifts_version": "1.0",
  "coderifts_governance": { "mcp_config": { "url": "https://app.coderifts.com/mcp", "transport": "streamable-http" }, "registry": "io.github.coderifts/api-governance", "manifest": "https://coderifts.com/mcp.json" },
  "detected_patterns": [ /* ... */ ],
  "breaking_changes_details": [ /* ... */ ],
  "compatibility_suggestions": [ /* ... */ ],
  "security_findings": [ /* ... */ ],
  "token_cost_impact": { /* ... */ }
}

Response Fields (Decision Spec core)

FieldTypeDescription
decisionstringOverall verdict: ALLOW, WARN, REQUIRE_APPROVAL, or BLOCK
safe_for_agentbooleanfalse if any agent-breaking change is present; agents MUST NOT call the API when false
risk_scorenumberOverall risk from 0 (safe) to 100 (critical)
breaking_changesnumberCount of breaking changes detected
patternsstring[]Named break patterns (e.g. AUTH_SCHEME_REMOVAL, TYPE_NARROWING)
requires_migrationbooleanWhether consumers must migrate before adopting
evidence_qualitystringConfidence band: LOW, MEDIUM, or HIGH
coderifts_versionstringDecision Spec version (currently 1.0)
timestampstringISO 8601 timestamp of the analysis

Extended Fields

Extended fields are additive and enrich the verdict. The frozen v1.0 core above never changes to accommodate them; an agent may read extended fields opportunistically and must not treat their absence as an error.

FieldTypeDescription
detected_patternsarrayNamed break patterns, each with a plain-English consequence
breaking_changes_detailsarrayPer-change blast radius: type, path/field, severity, description
coderifts_governanceobjectNative integration info: MCP url, registry id, manifest url
confidence_scorenumberNumeric verdict confidence (0-100); pairs with evidence_quality
compatibility_suggestionsarrayConcrete non-breaking remediation steps (change, field, suggestion, effort)
security_findingsarrayAuth/scope/security findings (e.g. auth scheme removed)
token_cost_impactobjectPer-call token and dollar delta of the change
pii_findingsarrayPII exposure findings; populated only when PII is detected
spec_extensionobjectCodeRifts x- spec extension data when present, else { "found": false }

MCP Tool Poisoning Gate

When an MCP manifest is supplied to POST /v1/mcp-diff (via old_mcp_manifest / new_mcp_manifest), the tool schemas are scanned for poisoning. Detected poisoning escalates the verdict; it never lowers it, and it does not change risk_score. When present, a poison_gate object is added to the response listing the finding types and tier.

TierFires onEffect
blockUnambiguous, co-signal-gated poison: suspicious_instruction, exfiltration_url_cosignal, file_read_exfiltration, text_injectiondecision is forced to BLOCK and safe_for_agent to false
raHeuristic anomalies: structural_anomaly, hidden_encodingdecision is raised to REQUIRE_APPROVAL only if currently lower; safe_for_agent is left unchanged

Precedence: any block-tier finding wins over ra-tier. A benign description edit with no sensitive co-signal produces no poison_gate and leaves the verdict unchanged.

Endpoint: POST /v1/graphql/diff

Analyze the difference between two GraphQL schemas and return a full governance report.

Request

cURL
curl -X POST \
  https://app.coderifts.com/api/v1/graphql/diff \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "base_schema": "schema { query: Query } type Query { hello: String }",
    "head_schema": "schema { query: Query } type Query { hello: String goodbye: String }"
  }'

Request Body

FieldTypeRequiredDescription
base_schema string required The base (old) GraphQL schema as a string
head_schema string required The head (new) GraphQL schema as a string

Response

JSON
{
  "decision": "ALLOW",
  "risk_score": 0,
  "safe_for_agent": true,
  "correlation_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
  "breaking_changes": 0,
  "changes": [],
  "non_breaking_changes": [
    {
      "type": "FIELD_ADDED",
      "severity": "NON_BREAKING",
      "name": "Query.goodbye",
      "description": "Field 'goodbye' was added to type 'Query'.",
      "impact": "Low"
    }
  ],
  "schema_type": "graphql",
  "analyzed_at": "2026-06-12T10:00:00.000Z",
  "coderifts_version": "1.0",
  "timestamp": "2026-06-12T10:00:00.000Z"
}

Response Fields

FieldTypeDescription
decisionstringOverall decision: ALLOW, WARN, REQUIRE_APPROVAL, or BLOCK
risk_scorenumberOverall risk score from 0 (safe) to 100 (critical)
safe_for_agentbooleantrue if no breaking changes, false otherwise
correlation_idstringUnique identifier for the request
breaking_changesnumberCount of breaking changes detected
changesarrayList of detected changes (breaking and non-breaking)
non_breaking_changesarrayList of non-breaking changes
schema_typestringType of schema analyzed (e.g., graphql)
analyzed_atstringISO 8601 timestamp of analysis
coderifts_versionstringVersion of the CodeRifts API
timestampstringISO 8601 timestamp of the response

Endpoint: POST /v1/grpc/diff

Analyze the difference between two gRPC/Protobuf schemas and return a full governance report.

Request

cURL
curl -X POST \
  https://app.coderifts.com/api/v1/grpc/diff \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "base_schema": "syntax = \"proto3\";\npackage helloworld;\nmessage HelloRequest { string name = 1; }\nservice Greeter { rpc SayHello (HelloRequest) returns (HelloReply); }",
    "head_schema": "syntax = \"proto3\";\npackage helloworld;\nmessage HelloRequest { string name = 1; }\nmessage HelloReply { string message = 1; }\nservice Greeter { rpc SayHello (HelloRequest) returns (HelloReply); rpc SayGoodbye (HelloRequest) returns (HelloReply); }"
  }'

Request Body

FieldTypeRequiredDescription
base_schema string required The base (old) Protobuf schema as a string
head_schema string required The head (new) Protobuf schema as a string

Response

JSON
{
  "decision": "ALLOW",
  "risk_score": 0,
  "safe_for_agent": true,
  "correlation_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
  "breaking_changes": 0,
  "changes": [],
  "non_breaking_changes": [
    {
      "type": "SERVICE_METHOD_ADDED",
      "severity": "NON_BREAKING",
      "name": "Greeter.SayGoodbye",
      "description": "RPC method 'SayGoodbye' was added to service 'Greeter'.",
      "impact": "Low"
    },
    {
      "type": "MESSAGE_ADDED",
      "severity": "NON_BREAKING",
      "name": "HelloReply",
      "description": "Message 'HelloReply' was added.",
      "impact": "Low"
    }
  ],
  "schema_type": "grpc",
  "analyzed_at": "2026-06-12T10:00:00.000Z",
  "coderifts_version": "1.0",
  "timestamp": "2026-06-12T10:00:00.000Z"
}

Response Fields

FieldTypeDescription
decisionstringOverall decision: ALLOW, WARN, REQUIRE_APPROVAL, or BLOCK
risk_scorenumberOverall risk score from 0 (safe) to 100 (critical)
safe_for_agentbooleantrue if no breaking changes, false otherwise
correlation_idstringUnique identifier for the request
breaking_changesnumberCount of breaking changes detected
changesarrayList of detected changes (breaking and non-breaking)
non_breaking_changesarrayList of non-breaking changes
schema_typestringType of schema analyzed (e.g., grpc)
analyzed_atstringISO 8601 timestamp of analysis
coderifts_versionstringVersion of the CodeRifts API
timestampstringISO 8601 timestamp of the response

Endpoint: POST /v1/asyncapi/diff

Analyze the difference between two AsyncAPI specifications and return a full governance report.

Request

cURL
curl -X POST \
  https://app.coderifts.com/api/v1/asyncapi/diff \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "base_schema": "asyncapi: 2.0.0\ninfo:\n  title: My API\n  version: 1.0.0\nchannels:\n  user/signedup:\n    publish:\n      message:\n        messageId: UserSignedUp\n        payload:\n          type: object\n          properties:\n            userId:\n              type: string",
    "head_schema": "asyncapi: 2.0.0\ninfo:\n  title: My API\n  version: 1.0.0\nchannels:\n  user/signedup:\n    publish:\n      message:\n        messageId: UserSignedUp\n        payload:\n          type: object\n          properties:\n            userId:\n              type: string\n            newOptionalField:\n              type: string"
  }'

Request Body

FieldTypeRequiredDescription
base_schema string required The base (old) AsyncAPI specification as a YAML or JSON string
head_schema string required The head (new) AsyncAPI specification as a YAML or JSON string

Response

JSON
{
  "decision": "ALLOW",
  "risk_score": 0,
  "safe_for_agent": true,
  "correlation_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
  "breaking_changes": 0,
  "changes": [],
  "non_breaking_changes": [
    {
      "type": "PAYLOAD_FIELD_ADDED",
      "severity": "NON_BREAKING",
      "name": "user/signedup.publish.UserSignedUp.newOptionalField",
      "description": "New payload field 'newOptionalField' was added to message 'UserSignedUp'.",
      "impact": "Low"
    }
  ],
  "schema_type": "asyncapi",
  "analyzed_at": "2026-06-12T10:00:00.000Z",
  "coderifts_version": "1.0",
  "timestamp": "2026-06-12T10:00:00.000Z"
}

Response Fields

FieldTypeDescription
decisionstringOverall decision: ALLOW, WARN, REQUIRE_APPROVAL, or BLOCK
risk_scorenumberOverall risk score from 0 (safe) to 100 (critical)
safe_for_agentbooleantrue if no breaking changes, false otherwise
correlation_idstringUnique identifier for the request
breaking_changesnumberCount of breaking changes detected
changesarrayList of detected changes (breaking and non-breaking)
non_breaking_changesarrayList of non-breaking changes
schema_typestringType of schema analyzed (e.g., asyncapi)
analyzed_atstringISO 8601 timestamp of analysis
coderifts_versionstringVersion of the CodeRifts API
timestampstringISO 8601 timestamp of the response

Endpoint: GET /v1/public/preflight

The zero-auth entry point for AI agents. Before calling or trusting an API, an agent passes the spec URL and receives a CodeRifts decision. Results are cached; if a spec has not been analyzed yet the endpoint returns PENDING and analysis is triggered in the background. A POST variant accepts an agent workflow body.

Request

cURL
curl "https://app.coderifts.com/api/v1/public/preflight?spec_url=https://example.com/openapi.json&debug=true"

Query Parameters

ParamTypeRequiredDescription
spec_urlstringrequiredURL of the OpenAPI spec to evaluate
debugbooleanoptionalWhen true, adds a debug block (cache lookup, score components, decision path)

Response

JSON
{
  "decision": "PENDING",
  "safe_for_agent": false,
  "correlation_id": "8c9ca61d-b0bd-4ae3-8117-3e3b8ad612db",
  "debug": {
    "cache_lookup": { "spec_hash": "sha256:9f523d36...", "cache_hit": false, "lookup_time_ms": 244 },
    "analysis": { "patterns_checked": 33, "score_components": { "S_contract": 0, "P_break": 0, "S_blast": 0, "S_agent": 0 } },
    "decision_path": ["analysis not yet complete \u2192 PENDING"]
  }
}

Endpoint: POST /v1/public/actionguard-check

Zero-auth, stateless check for a single GitHub Actions workflow diff. An agent passes the workflow file's before/after content and receives WARN-tier findings for risky uses: action references (unpinned refs, pin drift, major-version jumps, new third-party actions). This endpoint is advisory only: it contributes no risk_score and its decision is always ALLOW (no findings) or WARN (one or more findings) — never REQUIRE_APPROVAL or BLOCK. Nothing is stored.

Request

cURL
curl -X POST \
  https://app.coderifts.com/api/v1/public/actionguard-check \
  -H "Content-Type: application/json" \
  -d '{
    "filename": ".github/workflows/ci.yml",
    "base_content": null,
    "head_content": "name: ci\non: [push]\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: some-org/deploy-action@main"
  }'

Request Body

FieldTypeRequiredDescription
filenamestringrequiredThe workflow file path, e.g. .github/workflows/ci.yml
head_contentstringrequiredThe workflow file content after the change (max 512KB)
base_contentstring or nulloptionalThe workflow content before the change. null (or omitted) means the file is newly added
trusted_ownersstring[]optionalExtra trusted action owners. Merged with the built-in list (actions, github) — never replaces it

Response

JSON
{
  "decision": "WARN",
  "safe_for_agent": true,
  "risk_score": 0,
  "breaking_changes": 0,
  "requires_migration": false,
  "patterns": ["UNPINNED_ACTION_REF", "NEW_THIRD_PARTY_ACTION"],
  "evidence_quality": "low",
  "coderifts_version": "1.0",
  "analysis_type": "actionguard_workflow_diff",
  "actionguard": {
    "version": "actionguard-v1",
    "findings": [
      {
        "id": "UNPINNED_ACTION_REF",
        "severity": "high",
        "action": "some-org/deploy-action",
        "file": ".github/workflows/ci.yml",
        "line": 8,
        "details": "some-org/deploy-action@main is not pinned to a full 40-char commit SHA"
      }
    ],
    "parse_errors": []
  },
  "timestamp": "2026-07-14T00:00:00.000Z"
}

Endpoint: POST /v1/agent-abort-demo

Simulates an agent workflow against an API spec change and returns whether the agent should abort. Supports common frameworks (langgraph, autogen, crewai, openai-functions, anthropic-tools, generic). Execution-linkage for agent runtimes.

Request

cURL
curl -X POST \
  https://app.coderifts.com/api/v1/agent-abort-demo \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "framework": "langgraph",
    "workflow": { "steps": [ { "tool": "get_user", "endpoint": "GET /users/{id}" } ] },
    "old_spec": "...",
    "head_spec": "..."
  }'

Request Body

FieldTypeRequiredDescription
workflowobjectrequiredObject with a steps array describing the agent tool calls
frameworkstringoptionalAgent framework name
old_spec / new_specstringoptionalThe API spec before / after the change

Response

JSON
{ "decision": "ALLOW", "risk_score": 0, "safe_for_agent": true, "correlation_id": "..." }

Endpoint: POST /v1/instability-scan

AIDE V2 statistical instability detection. Accepts batched API traffic samples and returns MAD-based Z-score, coefficient of variation, trend slope, and named instability patterns (e.g. LATENCY_REGRESSION, PAYLOAD_FLAKINESS, ERROR_RATE_SPIKE, TOKEN_COST_DRIFT).

Request

cURL
curl -X POST \
  https://app.coderifts.com/api/v1/instability-scan \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "endpoint": "GET /users",
    "samples": { "latency_ms": [120, 135, 410, 980], "error_rate": [0.0, 0.0, 0.2, 0.5] }
  }'

Request Body

FieldTypeRequiredDescription
endpointstringrequiredThe API endpoint being analyzed
samplesobjectrequiredBatched numeric traffic samples (latency, error rate, payload size, token cost)

Endpoint: GET /v1/metrics

Operational telemetry for your account: request volume, cache efficiency, and pending/error counts over a rolling window.

Request

cURL
curl https://app.coderifts.com/api/v1/metrics \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

JSON
{
  "period": "last_24h",
  "preflight": { "total_requests": 292, "cache_hits": 0, "cache_hit_ratio": 0, "pending_count": 292, "error_count": 0 }
}

Endpoint: GET /v1/dashboard

Per-repository activity view for API owners: how many agents checked the spec and the distribution of BLOCK / WARN / ALLOW decisions.

Request

cURL
curl "https://app.coderifts.com/api/v1/dashboard?repo=owner/repo" \
  -H "Authorization: Bearer YOUR_API_KEY"

Query Parameters

ParamTypeRequiredDescription
repostringrequiredRepository in owner/name form

A companion GET /v1/cache/stats?repo=owner/name endpoint returns cache hit-ratio for the same repository.

Endpoint: GET /v1/crawler-stats

Tracks which AI crawlers and agents are calling the public preflight endpoint (by IP / User-Agent) over the last 7 days. The adoption signal for AI-agent traffic. No API key required.

Request

cURL
curl https://app.coderifts.com/api/v1/crawler-stats

Response

JSON
{
  "period": "last_7d",
  "total_preflight_calls": 2001,
  "ai_crawler_calls": 0,
  "ai_crawler_rate": 0,
  "crawlers": []
}

Endpoint: GET /v1/attestation/public-key

Channel-chain attestation lets an external party cryptographically verify that a verdict response came from CodeRifts and follows an unbroken chain from the previous verdict. Every verdict response can carry an Ed25519-signed chain_receipt; this endpoint publishes the public key used to verify it. No API key required.

Request

cURL
curl https://app.coderifts.com/api/v1/attestation/public-key

Response

JSON
{
  "kid": "k1",
  "alg": "Ed25519",
  "public_key_pem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n"
}

When no signing key is configured on the server, this endpoint returns 503 with { "error": "attestation_not_configured" }, and verdict responses simply omit the chain fields.

chain_receipt on verdict responses

When attestation is configured, POST /v1/action-verdict and POST /v1/verdict add a top-level chain_receipt string. It is a compact token of two base64url segments joined by a dot: base64url(body).base64url(signature). The receipt lives entirely in the response and never affects the deterministic verdict or its fingerprint. New receipts are v2 and add a signed reg field (the hash of the evidence-trust-registry state in force at issuance); older v1 receipts remain verifiable forever.

JSON (excerpt of a verdict response)
{
  "decision": "ALLOW",
  "chain_status": "absent",
  "chain_receipt": "eyJ2IjoyLCJraWQiOiJrMSIsImZwIjoic2hhMjU2Oi4uLiIsInByZXYiOiJudWxsIiwiY2FsbGVyIjoiYW5vbiIsInRzIjoiMjAyNi0wMS0wMVQwMDowMDowMC4wMDBaIiwicmVnIjoiNGY1M2NkYTE4YzJiYWEwYzAzNTRiYjVmOWEzZWNiZTVlZDEyYWI0ZDhlMTFiYTg3M2MyZjExMTYxMjAyYjk0NSJ9.f3Zk...signature"
}

The decoded body is self-describing so anyone can verify it with the published key:

FieldMeaning
vReceipt format version - 2 for new receipts, 1 for older ones (both remain verifiable)
kidKey id of the signing key (matches the public-key endpoint)
fpThe verdict_fingerprint this receipt attests
prevsha256 of the previous receipt, or the string null for the first link
callerCaller label (no secret material)
tsISO-8601 timestamp of issuance
regv2 only - hash of the evidence-trust-registry state in force at issuance. Signed but informational: verifiers do NOT compare it to the live registry, so a registry change never invalidates a prior receipt.

The signed bytes are a pipe-joined string, signed with Ed25519. For v2 it is crchain.v1|kid|fp|prev|caller|ts|reg; for v1 it is the same string without the trailing |reg (crchain.v1|kid|fp|prev|caller|ts), which remains verifiable. To verify: base64url-decode both segments, rebuild the string for the receipt's own version (checking the body v), and check the signature against the published public key.

prev_receipt and chain_status

To link a call to the previous one, pass the previous response's chain_receipt back as an opt-in top-level prev_receipt field in the request body. It is additive: omitting it changes nothing.

JSON (request body)
{
  "action_type": "tool_call",
  "provenance": { "channel": "ci_manifest", "issuer_trust": "trusted" },
  "tool": { "name": "get_customer", "capabilities": ["read"] },
  "memory": { "op": "read", "namespace": "working" },
  "prev_receipt": "eyJ2IjoxLCJraWQiOiJrMSJ9.f3Zk...signature"
}

Every verdict response reports a chain_status:

chain_statusMeaning
intactA valid prev_receipt was supplied and verified against the current key
absentNo prev_receipt was supplied (or attestation is off) - annotation only
brokenA prev_receipt was supplied but failed verification

A broken chain combined with a destructive-capability action escalates the decision to REQUIRE_APPROVAL (fail-closed). A broken chain on a read-only action, and any absent chain, are annotation only and never change the decision.

Key rotation

The current model is a single active signing key: one private key (CHAIN_SIGNING_KEY) and its key id (CHAIN_SIGNING_KID, default k1). To rotate:

  1. Generate a new Ed25519 keypair.
  2. Set CHAIN_SIGNING_KEY to the new private key and CHAIN_SIGNING_KID to a new id.
  3. Redeploy. New receipts are signed under the new kid; GET /v1/attestation/public-key serves the new public key.

After rotation, a prev_receipt that was signed under the OLD kid no longer matches the active key and verifies as unknown_kid, so its chain_status becomes broken (fail-closed). Per the rules above, a broken chain is annotation only unless the action is destructive, in which case it escalates to REQUIRE_APPROVAL - it never silently passes. Because the chain lives entirely in the request/response and never in server state, rotation needs no migration.

Multi-key verification - accepting one or more PREVIOUS public keys during a rotation window so in-flight chains stay intact across a rotation - is on the roadmap and is not supported today: exactly one kid is active at a time.

Independent verification

The receipt format above is public and frozen (see RECEIPT_FORMAT.md). An open-source verifier - Node (zero-dependency) and Python - is available at github.com/coderifts/receipt-verifier, so anyone can verify a chain_receipt without trusting CodeRifts. With no --key it fetches the public key from this endpoint by default.

Shell
git clone https://github.com/coderifts/receipt-verifier
cd receipt-verifier
node verify.js "<chain_receipt>"

Signed Evidence at Origin

Signed evidence is a third-party Ed25519-signed authorization carried in the request. Unlike chain attestation (which verifies our own signature), evidence verifies a THIRD-PARTY signature against a curated trust registry of registered public keys. Each evidence SCOPE relaxes exactly its own one gate, landing that gate at REQUIRE_APPROVAL instead of BLOCK (never lower); it can never suppress any other gate:

Scopes are isolated: a key trusted for one scope can never satisfy the other (both the registry-granted scope and the payload scope are checked). The mass_pii_exfiltration gate is never relaxable by any scope.

Request field

Pass a top-level evidence ARRAY of compact evidence tokens. It is additive and opt-in: omitting it (or sending an empty array) changes nothing.

JSON (request body)
{
  "action_type": "tool_call",
  "provenance": { "channel": "user_message", "issuer_trust": "untrusted" },
  "tool": { "name": "delete_records", "capabilities": ["destructive"] },
  "memory": { "op": "read", "namespace": "working" },
  "evidence": ["trusted-authz-1.eyJzY29wZSI6...payload.f3Zk...signature"]
}

Token format

Each token is three base64url segments joined by dots: kid.base64url(payloadJSON).base64url(sig). The signed bytes are the pipe-joined string crev.v1|kid|sha256hex(payloadB64) where payloadB64 is the exact transmitted middle segment (hashing the transmitted bytes avoids re-canonicalization ambiguity), signed with Ed25519.

The decoded payload is a JSON object. The scope selects which binding fields are required:

FieldMeaning
scopeuser_authorization or source_attestation
tooluser_authorization only: the tool name this authorization is for, or * for any tool
opuser_authorization only: the capability it authorizes (e.g. destructive); must be among the request tool's capabilities
content_hashsource_attestation only: sha256 hex of the request's memory.data (see below)
originsource_attestation only: optional origin label (informational in v1; no origin allow-list yet)
JSON (decoded payloads)
{ "scope": "user_authorization", "tool": "delete_records", "op": "destructive" }
{ "scope": "source_attestation", "origin": "acme-etl", "content_hash": "sha256hex(memory.data)" }

source_attestation: content binding

A source attestation binds to the actual data being acted upon. Pass that data as an opt-in top-level memory.data string; the origin signs content_hash = sha256hex(memory.data). The hash is over the RAW transmitted string exactly as received (no re-canonicalization - the same transmitted-bytes principle as the token signing input). The gate is suppressed only when a registry-trusted source_attestation key signs a content_hash equal to sha256hex of the request's memory.data. Omitting memory.data (or sending a non-string / empty value) means no content binding, so source_attestation can never fire - behavior is byte-identical to before.

Effect and fail-closed semantics

The gate is suppressed only on the full happy path: the token verifies against a registry-trusted kid AND its payload.scope is user_authorization AND payload.tool matches the request tool (or is *) AND payload.op is among the request capabilities. Every other case - absent, malformed, unknown kid, bad signature, wrong scope, tool or op mismatch - behaves EXACTLY as if no evidence were supplied (fail-closed). A suppressed A1 gate does not disable the other gates: an input that also triggers a data-laundering gate still lands on BLOCK.

Replay semantics: evidence is request-bound, not single-use. The payload embeds the tool and op it authorizes and is checked against the request; there is no nonce and no expiry comparison (any time field in the payload is opaque signed bytes, never compared to a clock). The same evidence deterministically re-authorizes the same action.

Response annotation

Under extended.evidence_detail (not part of the hashed verdict):

JSON (excerpt)
"evidence_detail": {
  "status": "valid",
  "kid": "trusted-authz-1",
  "gate_suppressed": "issuer_independence_untrusted_destructive"
}

status is valid (a token authorized this request), invalid (evidence present but nothing authorized it), or absent (no evidence). gate_suppressed is the suppressed gate name, or null.

The trust registry is curated: public keys are added only after review. Contact us to register a key.

Blast Radius Signals

Blast-radius signals are the INVERSE of signed evidence. Where each evidence scope RELAXES exactly one gate (BLOCK down to REQUIRE_APPROVAL), each blast-radius signal TIGHTENS: it lets a caller declare what a destructive or financial action targets, and the verdict escalates deterministically. These signals can only RAISE severity, never lower it, and their absence changes nothing (the same absent-is-no-signal principle as chain attestation and evidence).

They are caller-declared enums on the tool section. The deterministic core does NO config lookup - the vocabulary aligns with the domains[].sensitivity terms but is supplied per-request, not resolved from any config.

Request fields

Two opt-in strings on tool. Omitting them (or sending a non-string) changes nothing - the verdict and its fingerprint are byte-identical to before.

FieldValuesMeaning
tool.target_sensitivitylow / normal / high / criticalHow sensitive the target namespace/resource is.
tool.scopesingle / bulkWhether the action hits a single record or a bulk/wildcard set. This is a literal caller-declared flag; the core does not parse paths or interpret wildcards.
JSON (request body)
{
  "action_type": "tool_call",
  "provenance": { "channel": "ci_manifest", "issuer_trust": "trusted" },
  "tool": { "name": "purge", "capabilities": ["destructive"], "target_sensitivity": "critical", "scope": "bulk" },
  "memory": { "op": "delete", "namespace": "working" }
}

Escalation matrix

The signals only ever escalate a HIGH-IMPACT action (a tool whose capabilities include destructive or financial - both carry the same risk weight). Any other action is never escalated by these fields. Escalations are pushed as decision hints and joined by max-severity, so the WORST outcome across all signals and gates wins. (The blast_gate names below keep the _destructive suffix for both classes.)

Condition (all require destructive or financial)Escalates toblast_gate
target_sensitivity is high or criticalREQUIRE_APPROVALblast_high_sensitivity_destructive
scope is bulkREQUIRE_APPROVALblast_bulk_destructive
target_sensitivity is critical AND scope is bulkBLOCK (the only BLOCK path)blast_critical_bulk_destructive

When more than one rule fires, the most severe wins the blast_gate name, but every hint is still joined into the decision.

Absent and unknown values

Absent (or non-string) fields are no signal - never an escalation and never a relaxation. An UNRECOGNIZED enum value (a typo like critical!! or HUGE) is also treated as no signal - a caller mistake must not escalate - but it is annotated with blast_note: "unrecognized value ignored". Because these signals are tightening-only, declaring low or normal sensitivity never relaxes a verdict that another gate already raised (for example a mass_pii_exfiltration BLOCK stays BLOCK).

Response annotation

Under extended.blast_radius (not part of the hashed verdict; the signal reaches the fingerprint only through the escalated decision, exactly like data_trust):

JSON (excerpt)
"blast_radius": {
  "gate": "blast_critical_bulk_destructive",
  "note": null
}

gate is the fired rule name (or null when nothing escalated); note carries the unknown-value annotation (or null).

Reversibility (deferred)

A third axis - reversibility of the action - is deliberately deferred to a future version. It needs caller-proven semantics before it can tighten a verdict, so v1 ships the two target axes only and does not read any memory.reversible field.

Endpoint: POST /v1/keys GET DELETE

Self-service API key lifecycle.

MethodPathDescription
POST/v1/keysCreate a new API key
GET/v1/keys/info?key=cr_live_...Inspect a key (tier, limits, usage)
GET/v1/keysList your keys (authenticated)
DELETE/v1/keys/:prefixRevoke a key by its prefix

Request

cURL
curl "https://app.coderifts.com/api/v1/keys/info?key=cr_live_YOUR_KEY"

Response Headers & Debug Mode

Every decision response carries machine-readable headers so agents and proxies can read the verdict without parsing the body. Every response carries a correlation id for tracing.

HeaderExampleDescription
X-CodeRifts-DecisionWARNThe verdict: ALLOW / WARN / REQUIRE_APPROVAL / BLOCK
X-CodeRifts-Risk-Score25Risk score 0–100
X-CodeRifts-Safe-For-AgentfalseWhether the change is safe for an agent to proceed
x-correlation-id8c9ca61d-...Unique request id (also in the correlation_id body field)

Debug Mode

Append ?debug=true to a request to add a debug block explaining the decision: cache lookup, score components (S_contract, P_break, S_blast, S_agent), and a human-readable decision path.

cURL
curl -D - "https://app.coderifts.com/api/v1/graphql/diff" \
  -H "Content-Type: application/json" \
  -d '{"base_schema":"type Query { a: String }","head_schema":"type Query { b: Int }"}'
# X-CodeRifts-Decision: WARN
# X-CodeRifts-Risk-Score: 25
# x-correlation-id: 8c9ca61d-...

Response

200 OK — Success

JSON
{
  "risk_score": 42,
  "risk_level": "moderate",
  "risk_dimensions": {
    "revenue_impact": 6,
    "blast_radius": 4,
    "app_compatibility": 3,
    "security": 0
  },
  "semver_suggestion": "major",
  "breaking_changes": [
    {
      "type": "path.remove",
      "path": "/users",
      "method": "GET",
      "field": "",
      "severity": "high",
      "description": "Endpoint removed"
    }
  ],
  "non_breaking_changes": [],
  "security_findings": [],
  "changelog": {
    "breaking": ["**Removed** endpoint `GET /users`"],
    "added": [],
    "changed": [],
    "deprecated": []
  },
  "policy_violations": [],
  "should_block": true,
  "stats": {
    "total_changes": 1,
    "breaking_count": 1,
    "non_breaking_count": 0,
    "security_count": 0
  }
}

Response Fields

FieldTypeDescription
risk_scorenumberOverall risk score from 0 (safe) to 100 (critical)
risk_levelstringHuman-readable level: minimal, low, moderate, high, critical
risk_dimensionsobjectBreakdown: revenue_impact, blast_radius, app_compatibility, security
semver_suggestionstringSuggested version bump: major, minor, or patch
breaking_changesarrayList of breaking change objects (see below)
non_breaking_changesarrayList of non-breaking change objects
security_findingsarraySecurity-related findings (auth removal, scope changes)
changelogobjectAuto-generated changelog: breaking, added, changed, deprecated
policy_violationsarrayPolicy rule violations based on config
should_blockbooleantrue if risk exceeds threshold or blocked change types found
statsobjectSummary counts: total_changes, breaking_count, non_breaking_count, security_count

Breaking Change Object

FieldTypeDescription
typestringChange type (e.g., path.remove, request.property.removed, response.property.type-changed)
pathstringAPI path affected (e.g., /users/{id})
methodstringHTTP method (GET, POST, etc.)
fieldstringSpecific field name if applicable
severitystringlow, medium, or high
descriptionstringHuman-readable description of the change

Economic impact estimate (cost model)

When a change has breaking changes, responses that include an economic_impact object (and the PR-comment "Economic Impact Estimate" block) carry a dollar figure. It is a heuristic, labeled basis: "heuristic" - a rough order-of-magnitude derived from configurable assumptions, not a quote or a measured cost.

The model is: affected endpoints (breaking changes) x downstream consumers x average_migration_hours x engineer_rate (plus a testing multiplier and an optional rollback-risk line). The assumptions actually used are echoed back under economic_impact.assumptions (engineer_rate_usd_hour, average_migration_hours).

What it is not: it does not know your team's real rates, your actual migration scope, or downstream contracts - it cannot. Treat it as a prioritization signal, not a budget line.

Tune it in the cost section of .coderifts.yml; every field falls back to a documented default, so omitting the section changes nothing:

.coderifts.yml
cost:
  engineer_rate: 150            # USD/hour (default 150)
  average_migration_hours: 40   # per breaking change (default 40)
  testing_multiplier: 1.5       # testing effort vs development (default 1.5)
  rollback_cost_multiplier: 3   # rollback vs normal migration (default 3)
  budget_threshold: 50000       # optional; warns when the estimate exceeds it
  currency: USD                 # default USD

Try It Live

Send a real request to the API. This uses the unauthenticated demo endpoint — no API key needed.

Analyzing your specs...

      

Error Codes

The API returns standard HTTP status codes with a JSON error body:

JSON
{
  "error": "missing_spec",
  "message": "Both old_spec and new_spec are required"
}
Error CodeHTTP StatusDescription
missing_spec400Both old_spec and new_spec are required
invalid_spec400One or both specs are not valid OpenAPI (YAML/JSON parse error)
spec_too_large413Request body exceeds 5MB limit
invalid_api_key401Missing, invalid, or expired API key
rate_limited429Monthly or per-minute rate limit exceeded
server_error500Internal server error — contact [email protected]

SDKs & Integration Examples

Copy-paste examples for popular languages and CI/CD platforms.

cURL

Bash
curl -X POST https://app.coderifts.com/api/v1/diff \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $CODERIFTS_API_KEY" \
  -d @- <<EOF
{
  "old_spec": "$(cat old-api.yaml)",
  "new_spec": "$(cat new-api.yaml)"
}
EOF

Python

Python
import requests

with open("old-api.yaml") as f:
    old_spec = f.read()
with open("new-api.yaml") as f:
    new_spec = f.read()

response = requests.post(
    "https://app.coderifts.com/api/v1/diff",
    headers={
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}",
    },
    json={"old_spec": old_spec, "new_spec": new_spec},
)

result = response.json()
print(f"Risk Score: {result['risk_score']}/100")
print(f"Breaking Changes: {len(result['breaking_changes'])}")

if result["should_block"]:
    raise SystemExit("Blocked: breaking changes exceed threshold")

JavaScript (Node.js / fetch)

JavaScript
const fs = require('fs');

const oldSpec = fs.readFileSync('old-api.yaml', 'utf-8');
const newSpec = fs.readFileSync('new-api.yaml', 'utf-8');

const response = await fetch('https://app.coderifts.com/api/v1/diff', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${process.env.CODERIFTS_API_KEY}`,
  },
  body: JSON.stringify({ old_spec: oldSpec, new_spec: newSpec }),
});

const result = await response.json();
console.log(`Risk Score: ${result.risk_score}/100`);
console.log(`Breaking Changes: ${result.breaking_changes.length}`);

if (result.should_block) {
  process.exit(1);
}

GitHub Actions

YAML
name: API Contract Check
on: [pull_request]

jobs:
  api-diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check API Breaking Changes
        run: |
          npx @coderifts/cli diff old-api.yaml new-api.yaml \
            --ci --threshold 50
        env:
          CODERIFTS_API_KEY: ${{ secrets.CODERIFTS_API_KEY }}

GitLab CI

YAML
api-contract-check:
  stage: test
  image: node:20
  script:
    - npx @coderifts/cli diff old-api.yaml new-api.yaml --ci --threshold 50
  variables:
    CODERIFTS_API_KEY: $CODERIFTS_API_KEY
  allow_failure: false

Jenkins Pipeline

Groovy
pipeline {
    agent any
    environment {
        CODERIFTS_API_KEY = credentials('coderifts-api-key')
    }
    stages {
        stage('API Contract Check') {
            steps {
                sh 'npx @coderifts/cli diff old-api.yaml new-api.yaml --ci --threshold 50'
            }
        }
    }
}

Configuration Templates

Industry-specific .coderifts.yml templates for your repo. Choose your industry, copy the config, and drop it in your repository root.

YAML
Download

Drop this file as .coderifts.yml in your repository root. CodeRifts will automatically detect and apply it.

Risk band thresholds

Under risk_scoring you can tune where the risk score (0-100) crosses into the HIGH and CRITICAL bands. These control the risk band label shown in the PR comment only - they do not change whether a check passes or fails. Pass/fail is governed separately by policy.freeze_on_risk_score.

KeyDefaultRangeEffect
risk_scoring.high_risk_threshold700-100Score at/above this is labeled HIGH in the PR comment.
risk_scoring.critical_risk_threshold850-100Score at/above this is labeled CRITICAL in the PR comment.

Out-of-range or non-numeric values fall back to the defaults. Example:

.coderifts.yml
risk_scoring:
  high_risk_threshold: 70       # 0-100; HIGH band label in the PR comment
  critical_risk_threshold: 85   # 0-100; CRITICAL band label

policy:
  freeze_on_risk_score: 85      # this is what actually fails the check