unauthorized
401The key is missing, malformed or unknown. Header: WWW-Authenticate: Bearer. The handshake and GET /api/v1/verify/{report_id} need no key.
This request needs a valid RowAttest API key in the Authorization header.
For AI agents
RowAttest tests a Supabase project's Row Level Security (RLS) and signs what it finds: the attestation. Your agent can verify it before it connects to a Supabase-backed app or MCP server, with no key and no account, or hand the check to the MCP tool verify_attestation.
GET /.well-known/rowattest.json → signed summary → Ed25519 verify → subject binding → verdict
// input { "origin": "https://example.com", "project_ref": "abcdefghijklmnopqrst" } // result { "valid": true, "failed_check": null, "report_id": "RA-01M4DWC8VPVD6078NGF5T6F1D5", "issued_at": "2026-10-17T13:48:53Z", "subject_match": true, "findings_present": false, "evidence_class": "executed-api", "platform": "supabase", "label": null }
GET https://example.com/.well-known/rowattest.json200 OK · application/jsonThe file is self published. It only says where to look.attestations[0].report_id RA-01M4DWC8VPVD6078NGF5T6F1D5summary_url · summary_sha256 · keys_urlNewest first. Your agent reads the first entry and may read the rest.GET app.rowattest.com/api/reports/RA-01M4DW…/summarysha256(bytes) == summary_sha256A different hash means the pointer is stale. Stop.keys.json → key_id 27499e4bRFC 8785 canonical JSON → Ed25519 signatureSame five checks as rowattest.com/verify: key-mismatch, canonicalization, signature, report-sha256, verify-url.sha256(project_ref) == subject_sha256totals.leak 0 · totals.disagreement 0Bound to this database, signed by RowAttest, no findings in that run. Your agent decides what to do with it.·
01
An attestation is a RowAttest signed summary published by the owner of an app or MCP server, plus a small pointer file at a fixed path on their origin. The summary is JSON signed with Ed25519. It carries the totals of one run, the schema fingerprint of the database at that time, a SHA‑256 of the project ref it is about, an optional label and the report id. It never carries table names, policies or credentials. Anyone can verify it against the public keys at rowattest.com/keys.json. Trust comes from the signature and the binding, not from the file that points to it.
02
Six steps. Steps 1 to 5 are what verify_attestation does for you. Step 6 is yours.
{origin}/.well-known/rowattest.json. Missing or invalid means there is no attestation; your agent follows its own policy.attestations[0], then GET its summary_url. SHA‑256 the bytes as served and compare with summary_sha256. A mismatch means the pointer is stale: stop.keys_url (cache it), pick the key whose key_id matches, verify the Ed25519 signature. The five checks are the ones rowattest.com/verify runs: key-mismatch, canonicalization, signature, report-sha256, verify-url. Any failure means INVALID: stop.totals. If leak or disagreement is above zero, the attestation says findings present, not clean. Your agent decides; the file cannot hide this.subject_sha256. A mismatch, or no subject_sha256, means the proof is not bound to this database: stop.schema_fingerprint with the published algorithm and compare. Otherwise use issued_at and your own staleness rule.Do not want to write it? With any RowAttest API key, the MCP tool verify_attestation runs steps 1 to 5 and returns valid, failed_check, report_id, issued_at, totals, subject_match, findings_present, evidence_class, platform and label. Step 6 comes back as not checked unless you pass a fingerprint. Input: origin, project_ref (optional), fingerprint (optional). Server docs: /mcp.
verify_attestation
{ "origin": "https://example.com",
"project_ref": "abcdefghijklmnopqrst" }
03
Serve this at /.well-known/rowattest.json on the origin your agent will use: the app's public origin, or the MCP server's origin. Content type application/json. Schema v1:
{
"rowattest": "1",
"subject": { "origin": "https://example.com", "kind": "app" },
"attestations": [
{
"report_id": "RA-01M4DWC8VPVD6078NGF5T6F1D5",
"summary_url": "https://app.rowattest.com/api/reports/RA-01M4DWC8VPVD6078NGF5T6F1D5/summary",
"verify_url": "https://rowattest.com/verify/RA-01M4DWC8VPVD6078NGF5T6F1D5",
"summary_sha256": "<64 hex of the summary bytes as served>",
"issued_at": "2026-10-17T13:48:53Z"
}
],
"keys_url": "https://rowattest.com/keys.json"
}
kind is app or mcp-server.attestations is newest first.summary_url is the public summary route, GET /api/reports/{id}/summary, which serves the stored signed summary bytes.summary_sha256 pins those bytes; a different hash means stale.verify_url is the human page for the same report.keys_url points at RowAttest's trusted key list.summary_url and verify_url from it.summary_sha256 from the bytes as served: curl -s SUMMARY_URL | shasum -a 256./.well-known/rowattest.json as application/json.https://rowattest.com/keys.json. One key today, key_id 27499e4b. key_id is the first 8 hex of the SHA‑256 of the raw public key. Rotation adds a key and never removes one.
04
A label is recorded in the signed summary exactly as sent and is public once the summary is. Do not put names, tickets or secrets in a label. If you label a run with a migration set, send a hash of it, not the file names. 1 to 128 printable ASCII characters.
05
Every error from the REST API and the MCP server is one JSON object: error with code, message and docs_url, plus the extras a code carries. docs_url is this page followed by # and the code. The list is closed; each code below is an anchor.
{ "error": { "code": "not_found",
"message": "…",
"docs_url": "https://rowattest.com/agents#not_found" } }
The key is missing, malformed or unknown. Header: WWW-Authenticate: Bearer. The handshake and GET /api/v1/verify/{report_id} need no key.
This request needs a valid RowAttest API key in the Authorization header.
No project or run with that id for this key. Another account's project is not found too; the API never says whether it exists.
No project or run with that id was found for this key.
No signed report with that id, or for that project and label (GET /api/v1/attestations, GET /api/v1/verify/{report_id}).
No signed report was found with that id, or for that project and label.
The project already has a run waiting to start.
This project already has a run waiting to start. Wait for it, then try again.
No fit check has completed.
RowAttest has not checked which tables it can test in this project; run a fit check on your dashboard, then try again.
The fit check is still running.
RowAttest has not finished checking which tables it can test in this project; try again when the fit check on your dashboard is complete.
The project needs attention.
This project needs attention on your dashboard before it can run.
RowAttest cannot test this project yet because its latest fit check found no table RowAttest can test; the dashboard lists each table and the reason.
This run has not finished yet. Try again when its status is complete.
Four cases, all with a Retry-After header. More than 120 requests with one key in a minute:
Too many requests with this key. Wait a minute, then try again.
More than 30 requests without a valid key from one address in 10 minutes:
Too many requests without a valid API key from this address. Try again after the time in the Retry-After header.
More than 10 run starts for one project in an hour:
Too many run starts for this project in the last hour. Try again after the time in the Retry-After header.
More than 10 fit checks for one project in an hour, API and dashboard together:
This project was checked 10 times in the last hour. Wait up to an hour, then check again.
The month's run allowance is used up. Extras: allowance, used, resets_at.
Free tier includes 10 runs per calendar month. You have used 10. Your allowance resets at the start of next month (UTC). A signed report for a complete run is bought on that run's page at app.rowattest.com, never through the API.
Pro includes 200 runs per calendar month. You have used 200. Your allowance resets at the start of next month (UTC).
Header: Allow.
This endpoint does not accept that method.
No valid project_id:
The request needs a JSON body with a valid project_id.
Bad wait:
The wait value must be a whole number from 1 to 60.
Bad label:
label must be 1 to 128 printable ASCII characters.
This idempotency key was already used for a different request.
Extra: poll_after_seconds.
The signed report for this run is being prepared. Try again in a few seconds.
Extra: url (the run page).
This run has no signed report.
A database read failed; nothing was changed.
RowAttest could not complete this request just now. Try again in a moment.
This account has not accepted the RowAttest Terms of Service. Sign in at app.rowattest.com to accept them, then try again.
The run was not started because the account's plan does not include signed proofs. Signed proofs are included with RowAttest Pro. A run started without attest runs as usual and returns the unverified result.
This account's plan does not include signed proofs.
06
A PASS applies only to the checks in the full report, at the time of that run. It does not certify that the application is secure.
For an agent, a clean attestation means three things: the signature verifies, the subject binding matches the database it is about to use, and leak and disagreement are both zero in that run. It says nothing about tables that were not in the run and nothing about changes made after it.