Diffport

Technical Docs

How to connect it, read it, and build on it.

Everything needed to run Diffport, in six parts. What each number means, where the thresholds are, and what to send us when something looks wrong.

Getting started

Connecting takes about a minute and needs no credentials beyond approving a GitHub App. Everything after it happens on its own.

1
Install the GitHub App

From Connect a repository in the app. GitHub asks which repositories to grant, and you can start with one. The permissions requested are read-only — Diffport cannot push, open a pull request, or change a branch, because write access is never asked for.

2
Prove the installation is yours

Diffport asks GitHub which installations your account can reach and offers only those. Nobody else's installation is shown, or named. If you install from inside Diffport the return trip carries a one-time code that does the same job.

3
Pick the repositories

Adding one queues its open pull requests and starts a read of its history. Both run in the background, and the counters on the Connect screen move as they go.

4
Wait about five minutes

A cron job drains the queue every five minutes, so nothing is instant. The first results appear on Review and Overview once the first pass finishes.

Why the history read matters

Without it a new repository would show almost nothing until somebody happened to open a pull request. The backfill walks the existing commits so day one has real content rather than an empty dashboard waiting for activity.

What Diffport never touches

It does not connect to your database, hold any credential of yours beyond the GitHub App installation, or ask your AI vendor anything. The schema view is rebuilt from the migration files inside your pull requests — see Security for the full list of what is and is not granted.

Reading the numbers

Four values appear across most screens. Knowing exactly what each one counts is the difference between using them and arguing with them.

ValueRangeWhat it counts
sourceenum ai, mixed, human or untraced — the verdict for a commit or a pull request.
ai_pct0–100 Machine-written share of traced lines only. Untraced lines are not in the denominator, so this is never diluted by what could not be read.
coverage_pct0–100 How much carried a signal at all. This is the number that tells you whether to trust the one above it.
risk_score0–100 Banded as low, med or high. Weights are yours to set under Settings, Risk scoring.

Untraced is its own answer

Where no signal is found, Diffport reports untraced. It is not a claim that a person wrote it, and it is never quietly upgraded to human to make a dashboard look complete. Anything you build on this data should treat it as a third case rather than folding it into human.

A pull request that reads forty percent untraced is telling you something specific and useful: a tool your team uses is not being recognised. That is actionable in a way that ninety percent human is not. The reasoning is set out at length in Untraced is not human.

Confidence is the floor

When several signals contribute, the confidence reported is the weakest of them, not the strongest. A conclusion is exactly as sound as the worst step used to reach it. A commit with no signal carries no confidence at all rather than a low one — there is nothing to be confident about.

Machine-written is not scored as risky

Authorship does not raise the risk number. It changes how much review a change has to earn. Those are different claims and the product keeps them apart deliberately, which is why you will see high-AI, low-risk pull requests and the reverse.

Agents

The catalog is data, not a ranking. Adding a tool to it is a fact about detection and never an endorsement, and nobody can pay for a place in it.

Registering an identity

Under Settings → Provenance you can add an identity so Diffport recognises a tool your team uses. An identity is one of three things: a bot login that authors commits, an author email address, or a commit trailer line.

Seen asExampleUse when
app_actormy-agent[bot] The tool commits under its own account.
commit_trailerCo-Authored-By: … The tool signs the commit message.
manualanything else You know the mapping and want it applied.
Registering is retroactive in one direction only

New scans match the identity immediately. Commits already attributed keep the attribution they were given — nothing is silently rewritten underneath a report you have already issued. To pick up history, re-read the repository from the Repositories tab.

Trust scores

Each agent gets a score built from two counted things: the share of its merged work that was reverted, and the share that was reviewed at all. It is a function of those two numbers and nothing else — there is no model behind it and no judgement about the vendor.

An agent with no merged work says so rather than showing a zero, because a zero would read as a score rather than an absence.

When a tool is not detected

Send us the tool's name and one commit message it left, through Support. That is usually everything needed to add it to the catalog for every workspace.

Alerts

A rule is a set of conditions, a severity, and the channels it goes to. Every firing carries the evidence that produced it rather than a severity badge on its own.

Conditions

Each clause is a field, a comparison and a value. A rule fires when all of its clauses are true, or any, depending on the mode you pick.

FieldTypeNotes
risk_scorenumber0 to 100.
destructive_countnumberDestructive DDL statements in the change.
untraced_commitsnumberCommits carrying no signal.
coverage_pctnumber0 to 100.
ai_pctnumber0 to 100, of traced lines.
files_changednumberFiles in the pull request.
has_secretbooleanA secret appears in the diff.
no_testsbooleanNo test files touched.
risk_bandenumlow, med, high.
sourceenumai, mixed, human, untraced.

Comparisons are =, !=, >, >=, < and <=.

Where they go

Email reaches every member of the workspace who has alert emails switched on. Slack and webhook go to the destinations set up under Settings → Integrations, and a rule that asks for a channel with no destination configured simply sends nothing.

A firing does not repeat

Once a rule has fired for a pull request it will not fire again for it while that firing is open, so a branch under active work does not email everyone on every push.

Personal settings

Under Settings → Notifications each person controls their own alert, report and scan emails, a minimum severity, and a daily or weekly digest. Those are per person and do not affect anyone else. Quiet hours hold anything that fires inside your window and deliver it when the window closes, read in your own time zone — nothing is discarded, and a held alert arrives saying why it is late.

Reports

A report is generated once, stored, and served back byte for byte. The hash printed on it matches the file for as long as the file exists.

Verifying one

Every report carries a SHA-256. Hash what you were given and compare — if they match, nothing has been altered since it was issued. That is the whole claim of a signed attestation and it is checkable in one line.

Verify a downloaded report
shasum -a 256 report.json
# compare against the SHA-256 shown beside the report,
# or the X-Diffport-SHA256 header if you pulled it from the API

Formats

JSON, CSV and PDF, all three signed the same way. The PDF is laid out here rather than by a library — the same commits, the same summary, the same hash, in a form you can hand to somebody who is never going to open a CSV.

Schedules

Daily, weekly or monthly, at an hour you choose, over a window you choose, for one repository or all of them. Due schedules are picked up by the same five minute job that reads your repositories, so a run can land up to five minutes past its hour.

When a run fails the reason is recorded and shown beside the schedule — a window with no commits reports nothing to attest to rather than producing an empty report. It then waits for its normal turn instead of retrying in a loop.

Reports outlive the workspace

A report you have already downloaded stays verifiable whatever happens to your account, because the hash is a property of the file rather than of our database. What ends is the ability to sign anything new against that workspace.

API and webhooks

A read-only HTTP API over everything Diffport knows, and signed outbound webhooks for everything it decides.

Inbound

The Read API

Nine endpoints, bearer tokens scoped to one workspace, 600 requests an hour. Every endpoint is a GET; anything else returns 405.

Outbound

Alert webhooks

Signed JSON posted to a URL you own when a rule fires. HTTPS on port 443, and never to an address inside a private network.

Tokens

Issue one under Settings → Integrations → API. It is shown once and stored as a SHA-256 of itself, so if it is lost the only way forward is to revoke and reissue. Ten live tokens per workspace, and 600 requests an hour each.

Check a token works
curl -H "Authorization: Bearer dp_..." \
     https://api.diffport.com/v1/me

Verifying a webhook we send you

Each destination gets its own signing secret, shown once when you add it. Every delivery carries X-Diffport-Signature as sha256=… computed over timestamp + "." + body, with the timestamp in X-Diffport-Timestamp.

Check it. A webhook URL can leak — a signature cannot be forged without the secret, and an endpoint that accepts anything arriving at the right address is an endpoint anyone can write to.

Cross-workspace reads return 404

Asking for an object belonging to another workspace answers 404, not 403. A token should not be able to confirm that an id it cannot read exists.