Skip to content
Botscent

From the page to the server

How your code sends the page verdict to your server, and how the server keeps it apart.

A page report is the page verdict, sent by your code to your own server. The page half sends nothing on its own. Your code chooses a carrier, and the server reads the report with readReport.

app/checkout/client.ts
import { reportHeaders } from 'botscent'

export async function checkout() {
  return fetch('/api/checkout', { method: 'POST', headers: { ...reportHeaders('/api/checkout') }, body: 'cart=1' })
}

The carriers

Botscent has 3 carriers. Each carrier reads the page verdict at the moment your code sends it.

CarrierUse it forWhat the server gets
reportHeaders(url)A fetch that your code sendsA Botscent-Report header
data-botscent-fieldA POST form that the browser submitsA botscent form field
An argumentA server function that your code callsThe verdict object

A header for fetch

reportHeaders returns { 'Botscent-Report': <entry> } when two things are true. The page verdict is an agent, and url is on the page's own origin. In every other case, it returns {}. So a person's requests do not change, and no report goes to another origin.

Spread the headers into the request, as in the example above. With the script tag, use window.botscent.reportHeaders.

A field for forms

Add data-botscent-field to a form, or to an element inside the form:

checkout.html
<form method="post" action="/checkout" data-botscent-field>
  <input name="cart" value="1" />
  <button>Check out</button>
</form>

The form gets a botscent field only on its own submission: a click, Enter, or requestSubmit. All of these must also be true:

  • No listener canceled the submit event.
  • The page verdict is an agent.
  • The method is POST. A submit button's formmethod counts.
  • The action is on the same origin. A submit button's formaction counts.

A call to form.submit and a new FormData(form) never get the field, because their data can go anywhere. A form that a submit listener reads while the event is still in progress does not get the field either. If your code sends a form's data with fetch, use reportHeaders(url). Botscent writes nothing to the DOM.

An argument

Pass the object that verdict returns to a server function that your code calls. On the server, pass the object to readReport. readReport checks the object against the same format as the header.

On the server

Read the report, and keep it apart from the request's own verdict:

app/api/checkout/route.ts
import { combine, inspect, readReport } from 'botscent/server'

export async function POST(request: Request) {
  const own = await inspect(request)
  const report = readReport(request.headers.get('botscent-report'))
  return Response.json({ request: own, report, combined: combine(own, report) })
}

The response holds 3 verdicts: what the request declared, what the page reported, and the two joined.

readReport returns null for a value that is malformed, longer than 256 bytes, or of an unknown major version. Each reason of a parsed report gets the page. prefix, for example page.claude.marker.active. So a report never passes isVerified.

For a form, your code parses the body and passes the botscent field to readReport. Botscent never reads a request body. In Python, use botscent.read_report and botscent.combine.

How combine joins the two

combine(own, report) adds the report's evidence to the request's own verdict:

  • The joined verdict is an agent when either verdict is an agent.
  • Its reasons are the request's reasons, then the report's reasons.
  • Its name is the request's name. If the request has no name, it is the report's name.
  • A missing or empty report returns the request's own verdict unchanged.

Use the joined verdict to measure agent traffic and to adapt what you show.

Warning: Do not use a page report or a joined verdict to allow access. Scripts on the page can forge a report. Use isVerified on the request's own verdict.

What a report does not carry

  • It carries no observed value. It holds only the format version, the agent name and the reason ids.
  • It is not sent for a person. reportHeaders returns {}, and the form gets no field.
  • It does not go to another origin.
  • It does not say who performed the action that sent it.

When a report leaves the device, your site collects it. See Privacy.