# Website text publications - API v1

Example source: https://checkthisfile.com/developers/web-text-example.html, section ID `sample-specification`. This fictional specification illustrates a selected section and a verification link outside it. The registered history is separate from the source page's current text.

Register public text from a website you control, keep immutable versions, compare their history and export signed evidence. The web workflow retains the complete extracted text. This is separate from hash-only file registration, which does not upload document bytes.

## First publication

Use an active account owner or administrator and a scoped API key. GET requires `publications:read`; POST requires `publications:write`. API credentials remain server-side. Do not place them in a public web page. The existing hosted API request and integrity registration allowances apply. Automatic checks do not consume API request allowance.

Requests use `Authorization: Bearer YOUR_KEY`, `Content-Type: application/json` and a correct `Content-Length`. JSON request bodies are limited to 8 KiB. Responses have `{ data, error, meta }`. Downloads are plain UTF-8 text or JSON. The implemented contract is available at `/api/web-publications-openapi`.

1. `POST /api/v1/publications/origins` with `{ "url": "https://your-domain.example/page" }`. Place the returned token in the returned `challengeUrl`: `https://your-domain.example/.well-known/checkthisfile-verification.txt`. Return HTTP 200, `text/plain`, UTF-8 and the token only (a trailing newline is allowed). Origins include the scheme and hostname; each subdomain has its own verification. Keep the file available for future checks. Verification establishes observed technical control of the origin; it does not establish legal identity or authorship.
2. `POST /api/v1/publications/origins/ORIGIN_ID/verify` with `{}`. A new unverified challenge expires in seven days. Verification lasts 30 days, and each capture or check rechecks the ownership file. Prepare a fresh challenge when an expired verification can no longer be used.
3. `POST /api/v1/publications/captures` with `{ "url": "https://your-domain.example/page", "elementId": "main-content", "title": "Page title", "language": "en" }`. The exact page must return public HTTPS, HTTP 200, `text/html`, UTF-8 and uncompressed content. Redirects, private network addresses, credentials, query strings, fragments, cookies and script execution are unsupported. Review the returned text and hash. A preview expires in 15 minutes; at most five previews may be active per account.
4. `POST /api/v1/publications/versions` with `{ "captureId": "CAPTURE_UUID", "requestId": "PERSISTED_REQUEST_UUID", "publicConsent": true }`. `requestId` is a UUID generated and stored by the caller for this publication. Persist the request before sending it. Repeat the identical body after an uncertain response; success is deduplicated permanently without consuming another registration. Reusing the ID with different input returns 409. Do not generate another ID for a retry.
5. Add the returned version's verification URL as an ordinary HTML link. `GET /api/v1/publications/DOCUMENT_ID` returns the version payload and its URL. Public history: `/publications/DOCUMENT_ID`. A fixed version: `/publications/DOCUMENT_ID/versions/VERSION_ID`. Place the link outside the registered section or mark its container `data-checkthisfile-exclude`. It needs no remote script, image or widget.

For a new version, repeat the capture and publish steps with `documentId` and `expectedVersion` equal to the current version number. The source URL, section ID, title and language remain fixed for that history. A concurrent publication returns 409. Create a separate history for a different source. Previous versions keep their text while retention remains active. Automatic checks never publish new versions.

## Checks and results

`POST /api/v1/publications/DOCUMENT_ID/check` with `{}` requests a dated check. The operational worker schedules checks every six hours while retention is active, using bounded requests to the ownership file and page. A lease prevents concurrent checks. The latest stored observation is signed and identifies the particular version checked:

- `match`: the observed extracted text hash matched that version.
- `changed`: the extracted text hash differed.
- `unavailable`: the ownership or source could not be checked; it is not a mismatch.
- `unsupported`: extraction or encoding was unsupported; it is not a mismatch.

Always display the observation date. A delayed check is marked overdue, and current page status remains unknown. An observation cannot establish what every visitor received, intermediate changes between checks or content before the capture. No user acceptance events are collected.

`GET /api/v1/publications` lists the account's records. `POST /api/v1/publications/DOCUMENT_ID/versions/VERSION_ID/withdraw` with `{ "consent": true }` explicitly withdraws that version. Withdrawal changes its status while retaining the historical text and signed publication. It does not rewrite a published version or delete its history.

## Extraction profile: web-section-v1

The same dependency-free implementation runs on the service and in `checkthisfile/web-text` version 0.1.4. Supply complete, correctly nested HTML and one unique section ID beginning with a letter, followed by up to 79 letters, digits, underscores, periods, colons or hyphens. The section must be delivered by the source server.

Script, style, template, noscript, textarea and title are excluded. `hidden`, `aria-hidden="true"` and `data-checkthisfile-exclude` exclude their elements and descendants. Block elements insert line boundaries; ASCII whitespace is collapsed; non-breaking spaces become spaces; empty lines are removed; remaining lines use LF without a final newline. Unicode is preserved without normalization. Numeric entities and the fixed named entity table in the published module are decoded. Unknown named entities, duplicate IDs and ambiguous or incomplete nesting fail closed.

The profile compares text only. It does not execute scripts or compare CSS visibility, layout, images, URLs in links, linked documents, visitor-specific variants or legal meaning. A change to those elements may leave the text hash unchanged. Select the correct section and review its preview.

Limits per capture: 2 MiB HTML, 64 nesting levels, 20,000 parser tokens, 100,000 text characters, 500 lines and 256 KiB UTF-8 text. Free includes 5 active pages and 5 MiB retained text; Pro 50 pages and 100 MiB; Scale 250 pages and 500 MiB. All retained versions count toward storage. Each published version uses one existing monthly integrity registration. Checks are included.

## Retention and exported evidence

Text is encrypted at rest in the active database and made public only after explicit publication consent. It is retained only while the account and, where required, paid access remain active. At access expiration, the text is immediately unavailable to readers. For an active auto-renewing subscription whose renewal has not yet been reconciled, access remains blocked during a maximum 24-hour confirmation window. This is not free access or continuous retention: confirmed cancellation or loss of paid access expires the copy immediately, and an unresolved renewal expires it at the end of that window. Pending access is reported as `accessPending=true`; exports return 503. Maintenance marks the history expired and deletes its text copies from the active database. Renewal does not restore an expired history; a new publication starts a new history. Historical hashes, signatures, source metadata and expiration state remain. Deletion from active tables is not a guarantee that historical database pages or backups have already disappeared. Follow the service backup policy and export necessary evidence before access ends.

Public export endpoints:

- `/api/public/publications/DOCUMENT_ID/versions/VERSION_ID/text`
- `/api/public/publications/DOCUMENT_ID/versions/VERSION_ID/evidence`

Unavailable text after retention returns 410. Invalid evidence returns 503. Exports use no-store and noindex. Customer publication pages are not indexed; product and developer pages are indexed in English and Spanish.

The signed publication uses `urn:checkthisfile:web-publication:v1`, SHA-256 of the exact UTF-8 text, RFC 8785 JSON canonicalization and Ed25519. It binds the URL, section, profile, title, language, capture date, registration date, domain verification date and preceding signed envelope hash. Observation schema: `urn:checkthisfile:web-observation:v1`. JSON schemas: `/schemas/web-publication-v1.json` and `/schemas/web-observation-v1.json`.

Pin a trusted key independently. Key discovery is `/.well-known/editwitness-keys.json`; public material embedded in evidence is not its own trust anchor. `verifyWebPublicationEvidence(exportedEvidence, { keyId, jwk })` performs offline signature and exact-text verification. It verifies a supplied observation with the same trusted key; a rotated observation key must also be trusted separately and supplied as the optional third argument. It does not establish current key revocation, a complete predecessor chain, current page content or current retention. Its `currentOnlineStatus` is always `unknown`.

Service dates are not qualified electronic timestamps and do not prove original publication time. Domain control and hashes do not establish legal identity, authorship, truth, delivery, acceptance or legal compliance.

## Offline package

Version 0.1.4 is prepared as a downloadable package. Registry publication is separate from the hosted deployment. Install the versioned archive with `npm install ./checkthisfile-0.1.4.tgz`. Published npm version 0.1.3 does not include this entry point.

```js
import { extractWebText, hashWebText, verifyWebPublicationEvidence } from 'checkthisfile/web-text';
const { text } = extractWebText(html, { elementId: 'main-content' });
const fingerprint = await hashWebText(text);
const result = await verifyWebPublicationEvidence(exportedEvidence, trustedKey);
```

The module makes no network calls, needs no account or service credentials, emits no telemetry and consumes no hosted quota. Website fetching and publication are separate, explicit operations through the hosted API.
