Router Docs
This document specifies the public JSON produced by Thrum's Router. It is designed to be programmatically stable for downstream automation (e.g., the Executor) and act as a receipt for credit spend.
This tutorial is written for absolute beginners; there should be no barriers to entry for Web3 engineers and auditors.
The Router produces a deterministic, side-effect-free receipt that:
1) summarizes what Thrum detected about a repository at a high level, 2) returns an allow/deny decision (with debug help) for running a successful Scan, and 3) provides a handoff to subsequent pipeline stages.
This document specifies the public JSON shape intended for paying customers who receive a receipt:
- CLI users who request JSON output
- cloud API users who receive a Router Report from
upload.thrum.sh
Media Type: application/vnd.thrum.router-report+json
This string is the label for the Router report format. If you fetch a Router receipt over HTTP, you'll see it in the response header: Content-Type: application/vnd.thrum.router-report+json.
application/…: this is an application data format.vnd.: "vendor-specific," so Thrum defines this JSON shape, rather than it being a generic internet-wide standard format.thrum.router-report: the thing being returned (a receipt/report).+json: the body is valid JSON, so you can parse it with JSON tooling!
Compatibility and Versioning
Our JSON has a field called schema_version. That number tells you which edition of the Router output format you're looking at. This is useful so downstream tooling (that maybe your team integrates Thrum into) knows what to expect from Thrum.
This report’s schema_version follows Semantic Versioning (SemVer): MAJOR.MINOR.PATCH.
Reference: Semantic Versioning 2.0.0
What each number means
-
MAJOR (e.g.,
2.x.x→3.0.0)
Breaking change. Something about the report format changed in a way that could break existing integrations (for example: a field was removed/renamed, or a field’s type changed). If you built tools to parse this report, you should plan to update your parser for a new MAJOR. -
MINOR (e.g.,
2.1.0→2.2.0) Backward-compatible change. New information may be added (typically as new, optional fields) that won’t break older parsers if ignored. -
PATCH (e.g.,
2.2.1→2.2.2) Backward-compatible fix/clarification. Small corrections that don’t change the overall contract in a breaking way (for example: fixing a typo in docs, clarifying behavior).
Design goals
- Determinism: the same input produces the same plan and billing estimate.
- Receipt-first execution: the Router returns a receipt that the user must approve before any paid execution begins and credits are spent.
- Minimal disclosure: show what will run, what it needs, what it will cost, and what to do if rejected, without publishing the Router’s algorithm.
- Machine-consumable: stable schema with explicit versioning.
Top-level object
The Router returns a single JSON object:
- On success:
RouterPlan - On failure:
ProblemDetails(RFC 9457)
Content types
- Success:
application/json - Error:
application/problem+json
RouterPlan schema
Required fields
{
"schema_version": "1.0",
"router": { "...": "..." },
"decision": { "...": "..." },
"repository": { "...": "..." },
"project": { "...": "..." },
"execution": { "...": "..." },
"billing": { "...": "..." },
"receipt": { "...": "..." },
"warnings": [],
"next_actions": []
}
Field reference
schema_version (string, required)
Semantic version for this public schema (not your internal router version).
router (object, required)
Public metadata about the Router build that produced the output.
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | e.g., "thrum-router" |
version |
string | yes | public release/version tag |
build |
string | no | build id / git sha (public-safe) |
timestamp |
string | yes | ISO-8601 |
decision (object, required)
Router accept/reject decision for whether execution can proceed under the declared policy.
| Field | Type | Required | Notes |
|---|---|---|---|
status |
string | yes | "accept" | "reject" |
code |
string | yes | stable machine code (e.g., SAFE_EXEC_REQUIRED) |
message |
string | yes | user-facing, mitigable |
exit_code |
integer | yes | 0 if accept; non-zero if reject |
mitigations |
string[] | yes | actionable steps |
evidence |
string[] | yes | high-level reasons; must not leak proprietary heuristics |
repository (object, required)
Repository-level snapshot sufficient for user comprehension and billing.
| Field | Type | Required | Notes |
|---|---|---|---|
repo_fingerprint |
string | yes | stable id for the uploaded content (opaque) |
input |
object | yes | upload/source metadata (public-safe) |
size |
object | yes | file/LOC/bytes totals used for estimation |
languages |
object[] | no | aggregate only (no file listing) |
repository.input
| Field | Type | Required | Notes |
|---|---|---|---|
source |
string | yes | "upload" | "git" | "archive" |
label |
string | no | user-provided name (e.g., repo name) |
commit |
string | no | if source is git |
subdir |
string | no | if user selected a subdirectory |
repository.size
| Field | Type | Required | Notes |
|---|---|---|---|
files_total |
integer | yes | |
bytes_total |
integer | yes | |
loc_total |
integer | yes | |
loc_code |
integer | yes | preferred for pricing basis |
loc_comments |
integer | no | |
loc_blank |
integer | no |
project (object, required)
Public summary of what the Router inferred as the primary project.
| Field | Type | Required | Notes |
|---|---|---|---|
kind |
string | yes | e.g., "evm", "zk", "mixed" |
detected_toolchains |
string[] | yes | e.g., ["foundry","hardhat"] |
selected_root |
string | yes | relative path (no absolute FS paths) |
package_layout |
string | no | e.g., "single", "monorepo" |
notes |
string[] | no | public-safe hints |
execution (object, required)
What will happen if the user approves the receipt.
| Field | Type | Required | Notes |
|---|---|---|---|
mode |
string | yes | "hermetic" | "safe_exec" | "networked" |
requirements |
object | yes | permissions required to proceed |
plan |
object[] | yes | ordered stages (names + public parameters) |
artifacts |
object | no | where outputs will be written/returned |
execution.requirements
| Field | Type | Required | Notes |
|---|---|---|---|
network |
string | yes | "none" | "optional" | "required" |
build_scripts |
string | yes | "blocked" | "allowed" | "required" |
elevated_fs_access |
boolean | yes | typically false |
estimated_runtime_seconds |
object | no | { "p50": ..., "p95": ... } |
execution.plan[] stage object
| Field | Type | Required | Notes |
|---|---|---|---|
stage |
string | yes | e.g., "compile", "static_detectors", "dynamic", "triage" |
description |
string | yes | user-readable |
inputs |
string[] | no | e.g., "repository" |
outputs |
string[] | no | e.g., "build_artifacts", "findings" |
billing (object, required)
The user must be able to review the estimate and approve a receipt pre-execution.
| Field | Type | Required | Notes |
|---|---|---|---|
unit |
string | yes | "credits" |
estimate |
object | yes | total + bounds |
breakdown |
object[] | yes | line items by stage |
pricing_version |
string | yes | public pricing schedule/version id |
notes |
string[] | no | disclaimers, rounding, etc. |
billing.estimate
| Field | Type | Required | Notes |
|---|---|---|---|
total |
integer | yes | point estimate |
low |
integer | no | optional lower bound |
high |
integer | no | optional upper bound |
basis |
object | yes | what measurements were used (public-safe) |
billing.estimate.basis
Allowed fields should be limited to measurements users can reason about (e.g., LOC basis), but not internal heuristics.
{
"loc_code": 48231,
"toolchains": ["foundry"],
"execution_mode": "safe_exec"
}
billing.breakdown[]
| Field | Type | Required | Notes |
|---|---|---|---|
item |
string | yes | e.g., "compile", "static_detectors" |
credits |
integer | yes | |
details |
object | no | public-safe, non-proprietary |
receipt (object, required)
This is the object the UI asks the user to approve.
| Field | Type | Required | Notes |
|---|---|---|---|
receipt_id |
string | yes | opaque |
status |
string | yes | "requires_approval" | "approved" | "expired" |
expires_at |
string | yes | ISO-8601 |
line_items |
object[] | yes | mirrors billing breakdown |
total_credits |
integer | yes | must equal billing.estimate.total |
approval |
object | yes | what the user must do next |
receipt.approval
| Field | Type | Required | Notes |
|---|---|---|---|
required |
boolean | yes | true for paid execution |
method |
string | yes | e.g., "POST /v1/receipts/{id}/approve" |
token |
string | no | optional approval token (short-lived) |
warnings (array, required)
Non-fatal issues to show users, e.g., “Multiple toolchains detected; selected foundry root.”
next_actions (array, required)
User-facing action prompts, e.g.:
- “Approve receipt to begin execution.”
- “Enable network access or switch to hermetic mode.”
Error format (RFC 9457)
When the Router cannot produce a plan (bad input, invalid archive, internal failure), return Problem Details for HTTP APIs (RFC 9457). At minimum: type, title, status, detail, instance.
Example:
{
"type": "https://thrum.sh/problems/invalid-archive",
"title": "Invalid repository archive",
"status": 400,
"detail": "The uploaded file could not be unpacked as a supported archive format.",
"instance": "req_01JFD...Z9"
}
Examples
Example A: Accepted
{
"schema_version": "1.0",
"router": {
"name": "thrum-router",
"version": "0.9.3",
"timestamp": "2025-12-16T05:58:12Z"
},
"decision": {
"status": "accept",
"code": "ACCEPT",
"message": "Ready to execute under safe execution mode.",
"exit_code": 0,
"mitigations": [],
"evidence": ["Primary project root selected: contracts/"]
},
"repository": {
"repo_fingerprint": "repo_9f2c...8a",
"input": { "source": "upload", "label": "my-protocol" },
"size": {
"files_total": 1249,
"bytes_total": 18392012,
"loc_total": 61210,
"loc_code": 48231
}
},
"project": {
"kind": "evm",
"detected_toolchains": ["foundry"],
"selected_root": "contracts/",
"package_layout": "single"
},
"execution": {
"mode": "safe_exec",
"requirements": {
"network": "optional",
"build_scripts": "blocked",
"elevated_fs_access": false
},
"plan": [
{ "stage": "compile", "description": "Build project artifacts for analysis." },
{ "stage": "static_detectors", "description": "Run static detectors over sources and artifacts." },
{ "stage": "triage", "description": "Normalize and package findings." }
]
},
"billing": {
"unit": "credits",
"pricing_version": "2025-12-public-01",
"estimate": {
"total": 120,
"low": 110,
"high": 160,
"basis": { "loc_code": 48231, "toolchains": ["foundry"], "execution_mode": "safe_exec" }
},
"breakdown": [
{ "item": "compile", "credits": 25 },
{ "item": "static_detectors", "credits": 85 },
{ "item": "triage", "credits": 10 }
],
"notes": ["High bound may apply if compilation requires additional passes."]
},
"receipt": {
"receipt_id": "rcpt_01JFD...K2",
"status": "requires_approval",
"expires_at": "2025-12-16T06:28:12Z",
"line_items": [
{ "item": "compile", "credits": 25 },
{ "item": "static_detectors", "credits": 85 },
{ "item": "triage", "credits": 10 }
],
"total_credits": 120,
"approval": { "required": true, "method": "POST /v1/receipts/rcpt_01JFD...K2/approve" }
},
"warnings": [],
"next_actions": ["Approve receipt to begin execution."]
}
Example B: Rejected
{
"schema_version": "1.0",
"router": {
"name": "thrum-router",
"version": "0.9.3",
"timestamp": "2025-12-16T06:02:12Z"
},
"decision": {
"status": "reject",
"code": "NETWORK_REQUIRED",
"message": "This project requires network access to install dependencies before compilation.",
"exit_code": 21,
"mitigations": [
"Re-run with network enabled, or",
"Upload a vendored dependency tree, or",
"Provide a prebuilt artifact bundle if supported"
],
"evidence": ["Dependency installation required for compilation."]
},
"repository": {
"repo_fingerprint": "repo_1b7a...91",
"input": { "source": "upload", "label": "my-protocol" },
"size": { "files_total": 980, "bytes_total": 12912001, "loc_total": 42110, "loc_code": 31880 }
},
"project": {
"kind": "evm",
"detected_toolchains": ["hardhat"],
"selected_root": ".",
"package_layout": "single"
},
"execution": {
"mode": "hermetic",
"requirements": { "network": "required", "build_scripts": "required", "elevated_fs_access": false },
"plan": []
},
"billing": {
"unit": "credits",
"pricing_version": "2025-12-public-01",
"estimate": { "total": 0, "basis": { "loc_code": 31880, "toolchains": ["hardhat"], "execution_mode": "hermetic" } },
"breakdown": []
},
"receipt": {
"receipt_id": "rcpt_none",
"status": "expired",
"expires_at": "2025-12-16T06:02:12Z",
"line_items": [],
"total_credits": 0,
"approval": { "required": false, "method": "" }
},
"warnings": [],
"next_actions": ["Enable network/build scripts or adjust inputs, then re-run Router."]
}