---
title: "HTTP scan API reference | Settlens"
description: "Settlens HTTP scan API: POST /api/scan with a bearer token, request fields, the JSON report response, errors, rate limits and destination (SSRF) rules."
url: https://settlens.com/docs/api
last_updated: 2026-10-13
---

# HTTP scan API

> **In short:** POST /api/scan on settlens.com runs the same passive scan as settlens scan on one public URL, with an optional facilitator, and returns the JSON report. It needs a bearer token issued to pilots, allows 30 requests per minute per client IP, refuses private destinations and never runs the active probe.

Run a passive Settlens scan from your own backend with one authenticated POST request and get the JSON report back.

The Settlens website exposes one scan endpoint for server-to-server use. It runs the same passive scan as [`settlens scan`](https://settlens.com/docs/cli#scan) on a public URL and returns the JSON report, with its [integrity digest](https://settlens.com/docs/reports#integrity), in the response body. It never sends a payment signature, never runs the active probe and never calls `/settle`.

> **Looking for the agent interface?**
>
> AI agents should use the [MCP server](https://settlens.com/docs/mcp) instead: its remote endpoint at `https://settlens.com/mcp` offers the offline verification tools publicly and the passive network tools with the same token as this API.

## Authentication

Every request needs `Authorization: Bearer <token>`; the `Bearer` scheme is required. The token is compared in constant time with the one configured on the server; a missing or wrong token gets `401`. A server with no token, a placeholder token or one shorter than 32 characters disables the API and answers `401` to everyone. Tokens are not self-service: ask for one with your [pilot request](https://settlens.com/contact). Call the API from a backend only: it sends no CORS headers, and a token in browser code is a token anyone can read.

## Request

**POST /api/scan**

```http
POST /api/scan HTTP/1.1
Host: settlens.com
Authorization: Bearer <your API token>
Content-Type: application/json

{ "url": "https://api.merchant.example/v1/weather", "facilitator": "https://facilitator.example" }
```

_Request body fields (JSON object, at most 32 KiB)_

| Field | Required | Description |
| --- | --- | --- |
| url | Yes | The paid resource to scan: an absolute http or https URL of at most 2048 characters that passes the destination rules below. |
| facilitator | No | Base URL of a facilitator to cross-check with GET /supported: a string of at most 2048 characters (an object is refused) that passes the same destination rules. |

The scan is fixed: `GET`, no custom headers, two samples, an 8000 ms timeout per request and no local policy, so policy checks do not run. To scan with a policy, custom headers or the authorized active probe, use the [CLI](https://settlens.com/docs/cli) in your own environment.

**With curl**

```shell
curl -sS https://settlens.com/api/scan \
  -H "Authorization: Bearer $SETTLENS_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://api.merchant.example/v1/weather"}' \
  -o report.json
```

## Response

A successful call answers `200` with the complete report: the same schema as the CLI's JSON report, with `metadata.source` set to `settlens-web-api`. Findings are part of the report, not HTTP errors: an endpoint that is not an x402 resource still gets `200` and a report with failing checks. Excerpt of a real call against a public page that does not ask for payment:

**200 OK (excerpt)**

```http
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-store

{
  "schemaVersion": 1,
  "reportId": "a01d0965",
  "product": "Settlens",
  "metadata": { "source": "settlens-web-api" },
  "scope": { "targetCount": 1, "activeTargetCount": 0, "settlementCalls": 0, "privateKeysUsed": 0 },
  "summary": { "pass": 3, "fail": 2, "warn": 0, "skip": 1 },
  "targets": [
    {
      "mode": "passive",
      "checks": [
        { "id": "transport.status_402", "status": "fail", "severity": "high",
          "message": "Expected HTTP 402 but received HTTP 200.", "…": "…" }
      ],
      "…": "…"
    }
  ],
  "integrity": {
    "algorithm": "sha256",
    "canonicalization": "settlens-stable-json-v2",
    "digest": "eb165ca0664092eee3f321d0b4643de8b4df02eae3f4a143033d09c315645bad"
  }
}
```

Save the body as a file and it verifies like any other report: `settlens verify-report report.json`. Field reference: [JSON report](https://settlens.com/docs/reports#json).

## Errors

Errors are JSON objects with a single `error` message.

_Error responses_

| Status | When | Example body |
| --- | --- | --- |
| 400 | Body is not a JSON object, url is missing or too long, facilitator is not a URL string of at most 2048 characters, or a destination is refused | {"error":"Private or non-routable destinations are not allowed."} |
| 401 | Missing or wrong bearer token | {"error":"Authentication required."} |
| 405 | Any method other than POST (with Allow: POST) | {"error":"Method not allowed."} |
| 413 | Body larger than 32 KiB | {"error":"Request body is too large."} |
| 429 | More than 30 requests in 60 seconds from one client IP, failed authentications included | {"error":"Rate limit exceeded."} |
| 500 | Unexpected server error | {"error":"Internal server error."} |

Other destination messages you may see: `Only http and https destinations are allowed.`, `Only ports 80 and 443 are allowed.`, `Credentials in the URL are not allowed.`, `Local destinations are not allowed.`, `The destination resolves to a private or non-routable address.` and `Could not resolve <host>: …`.

## Limits

- 30 requests per client IP per 60-second window. Requests rejected with `401` count too.
- Request bodies up to 32 KiB; `url` up to 2048 characters.
- Each scan makes up to two requests to the target (one when the first answer has no `PAYMENT-REQUIRED`), plus one to `/supported` when a facilitator is given, each with an 8000 ms timeout and a 1 MiB cap on the body read.

## Destination rules (SSRF protection)

The API makes requests from Settlens' server, so it only reaches public destinations. Before any request, the target and facilitator URLs are checked:

- Only `http` and `https`, on ports 80 and 443 only, with no credentials in the URL.
- No `localhost`, and no host name ending in `.local` or `.internal`.
- An IP literal, and every address a host name resolves to, must be publicly routable. Refused IPv4 ranges include private networks (10/8, 172.16/12, 192.168/16), loopback (127/8), link-local (169.254/16, which covers cloud metadata services), shared CGNAT space (100.64/10), documentation, benchmarking, multicast and reserved ranges. For IPv6 only global unicast is accepted, without documentation and Teredo prefixes, and an IPv4 address embedded in IPv6 (mapped, NAT64 or 6to4) must itself be public.
- Redirects are never followed, so a public URL cannot bounce the request to an internal one.

The HTTP client resolves the host again when it connects, after the check. That gap is accepted because the API is authenticated, passive and follows no redirects.

## What is not an API

`/api/pilot` receives the website's pilot request form. It is not a public API and has no stability guarantees; use the [contact page](https://settlens.com/contact) to reach the team.

## Questions

**Can the API run the active probe?**

No. The API only runs passive scans. The authorized active probe is available only in the CLI, where you run it yourself with --active --authorized --authorization-ref.

**Can I scan an endpoint on my private network?**

Not through the API: private, loopback, link-local and other non-public destinations are refused. Run the CLI or the GitHub Action inside your network instead.

**Does the API store the reports?**

The endpoint returns the report in the response and does not write it anywhere. Keep the response body if you need it as evidence.

## Sources

1. [RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://www.rfc-editor.org/rfc/rfc6750.html) — IETF
2. [RFC 6890: Special-Purpose IP Address Registries](https://www.rfc-editor.org/rfc/rfc6890.html) — IETF
