Lifub API Reference

Version 1.0.3OpenAPI 3.1.0Base URL https://api.lifub.comOpenAPI JSON

Every endpoint below is generated from the same document, so this page and the JSON cannot disagree.

echo

Request echo and fraud detection endpoints

GET /echo.json

Echo request details with fraud detection
Returns detailed information about the incoming request including headers, IP address, fingerprint, session hash, and fraud detection signals. Useful for debugging, bot detection, and request inspection. Pass `?status=NNN` to override the default 200 response code (any valid HTTP status 100–999; invalid/absent values fall back to 200).
Responses
StatusContentDescription
200application/json EchoEcho response with request details
500Internal server error
Example request
curl -sS 'https://api.lifub.com/echo.json'

entropy

Random number generation

GET /entropy.json

Generate random entropy values
Returns a collection of random values including UUID, integers, floats, and a Gaussian-distributed number. Uses a fast (non-cryptographic) RNG.
Responses
StatusContentDescription
200application/json EntropyResponseEntropy values generated successfully
500Internal server error
Example request
curl -sS 'https://api.lifub.com/entropy.json'

encoding

Text encoding utilities (MD5, SHA1, SHA256, Base64)

GET /encoding.json

Encode and hash text values
Computes various encodings (Base64, URL) and hashes (MD5, SHA1, SHA256) for the given input value. Also provides octal, decimal, and hex byte representations.
Parameters
NameInTypeRequiredDescription
valuequerystringnoRaw input value to encode/decode.
charsetquerystringnoCharset name, e.g. `UTF-8`, `ISO-8859-1`, etc. Defaults to `UTF-8` (matching the Kotlin version's `Charsets.UTF_8.name()`).
Responses
StatusContentDescription
200application/json EncodingResponseEncoding results
500Internal server error
Example request
curl -sS 'https://api.lifub.com/encoding.json'

whois

IP geolocation and WHOIS lookup

GET /whois.json

IP geolocation and ASN lookup
Returns GeoIP + ASN data for the caller's IP: country, city, ASN and ISP. Data source: DB-IP Lite (CC BY 4.0). Free-tier responses include the extended fields (`code` postal, `timeZone`, `countryState`, `cityId`, `countryStateId`) in the response shape with sentinel placeholders — contact info@lifub.com to switch a key onto the Pro/Enterprise MaxMind backend that returns real values. This is IP intelligence, NOT RDAP / domain-registration WHOIS. Auth: anonymous callers are rate-limited per IP; authenticated callers pass a key via the `X-API-Key` header, which also governs the monthly quota for that key. The optional `clientId` query parameter is a client-supplied request ID for log correlation; it is NOT used for authentication or billing.

Auth Optional X-API-Key: <API key> - Optional API key. Anonymous callers are rate-limited per IP; a key raises the limit and meters usage.

Parameters
NameInTypeRequiredDescription
queryIPquerystringnoOverride IP address for lookup
clientIdquerystringnoOptional client-supplied request ID for log correlation. NOT auth.
Responses
StatusContentDescription
200application/json WhoisGeoIP + ASN data for the caller or the overridden IP
429Rate limit or monthly quota exceeded
500Internal server error
Example request
curl -sS 'https://api.lifub.com/whois.json'

inspect

Request inspection utilities

GET /inspect.json

Request inspection combined with IP geolocation and ASN
Returns the incoming request (headers, request-body echo, a per-request fingerprint, and ASN-bumped fraud / bot-likelihood signals) combined with GeoIP + ASN data for the caller. Data source: DB-IP Lite (CC BY 4.0); extended geolocation fields (postal, time zone, subdivision) return sentinel placeholders unless the key is on the Pro/Enterprise MaxMind backend. This is IP intelligence, NOT RDAP / domain-registration WHOIS. Auth: anonymous callers are rate-limited per IP; authenticated callers pass a key via the `X-API-Key` header, which also governs the monthly quota for that key. Any tier (Free / Pro / Enterprise) may call this endpoint — plan gating is on quota and extended fields, not on endpoint access. Supports the same optional `queryIP` override and `clientId` log-correlation parameters as `/whois.json`. The debug-only `response-header` parameter echoes caller-specified headers back in the response, restricted to a small allowlist of safe cache / content-negotiation headers.

Auth Optional X-API-Key: <API key> - Optional API key. Anonymous callers are rate-limited per IP; a key raises the limit and meters usage.

Parameters
NameInTypeRequiredDescription
queryIPquerystringnoOverride IP address for geolocation lookup
clientIdquerystringnoOptional client-supplied request ID for log correlation. NOT auth.
response-headerquerystringnoDebug: echo `Name: Value` back as a response header. Restricted to a safe allowlist.
Responses
StatusContentDescription
200application/json InspectionInspection data combining request echo and IP geolocation
429Rate limit or monthly quota exceeded
500Internal server error
Example request
curl -sS 'https://api.lifub.com/inspect.json'

curl

HTTP fetch proxy

GET /curl.json

HTTP fetch proxycostly
Fetches the specified URL and returns the response status code and body. Targets must be public http/https endpoints; loopback, RFC 1918, link-local, and other non-routable addresses are refused. Response body is capped at 5 MiB.
Parameters
NameInTypeRequiredDescription
urlquerystring | nullnoURL to fetch
Responses
StatusContentDescription
200application/json CurlResponseSuccessfully fetched URL
400URL missing, malformed, or pointing at a disallowed target
404Failed to fetch URL (network error, etc.)
413Upstream body exceeded the 5 MiB cap
502DNS resolution for the target failed
Example request
curl -sS 'https://api.lifub.com/curl.json'

audit

Bounded HTTP/security and technical SEO site audits

GET /audit.json

Audit one page's HTTP/security health and technical SEO readinesscostly
Fetches a public page and a bounded set of same-origin well-known files. The legacy HTTP/security health score and the separately versioned source-HTML technical SEO score have independent opt-in gates; neither is a ranking score. Anonymous callers receive 10 completed reports, then a full one-hour cooldown starts when the tenth report completes. An operator-issued X-Site-Audit-Voucher bearer UUID adds 100 completed reports and bypasses proof-of-work, but not scanner safety limits.

Auth Optional X-Site-Audit-Voucher: <audit voucher> - Optional operator-issued Site Audit voucher UUID: adds paid reports and skips the proof of work.

Parameters
NameInTypeRequiredDescription
urlquerystring | nullnoOrigin/page URL to audit.
formatquerystring | nullno`?format=junit` → JUnit XML instead of JSON (CI-friendly). Any other value (or absent) yields JSON.
gatequeryinteger | null (int32)no`?gate=N` → a CI gate: respond `422 Unprocessable Entity` when the audit score is below `N` (so `curl --fail` blocks a deploy), else `200`.
seoGatequeryinteger | null (int32)no`?seoGate=N` → independently gate the complete technical SEO score. Missing, interrupted, or otherwise unscored SEO analysis fails closed.
expectIndexablequeryboolean | nullnoWhether this URL is intended to be indexable. Defaults to `true`; set `expectIndexable=false` for deliberate noindex/excluded pages.
sharequeryboolean | nullno`?share=true` → also publish this completed report under an unguessable id so it can be handed to someone else as a link, and return that id as `shareId`. Off by default: an automated or CI caller stores nothing. Sharing never re-runs the audit and never spends an extra credit.
Responses
StatusContentDescription
200application/json AuditReport
application/xml string
Audit completed; JUnit XML is returned when format=junit
400URL is missing, malformed, or disallowed by the SSRF policy
402The supplied Site Audit voucher has no paid reports remaining
403The supplied Site Audit voucher is invalid or revoked
409Paid credits exist but are temporarily reserved by audits in progress; retry after the Retry-After interval
422application/json AuditReport
application/xml string
The requested legacy gate or SEO gate failed
429Free one-hour quota, per-target, or anonymous safety limit exceeded
502Target DNS resolution failed
503The durable Site Audit quota backend is unavailable
Example request
curl -sS 'https://api.lifub.com/audit.json'

GET /audit/crawl

Run a bounded sampled site crawlcostly
Unions sitemap, homepage-link, and optional Site Search discovery; reports per-URL provenance, strict bounded sitemap diagnostics, sampled link/site findings, and separate HTTP-health and technical SEO summaries. Coverage is always sampled and resource caps are explicit.
Parameters
NameInTypeRequiredDescription
urlquerystring | nullno
limitqueryinteger | nullnoNumber of pages to audit, clamped to `1..=MAX_PAGES`.
expectIndexablequeryboolean | nullnoWhether sampled pages are intended to be indexable. Defaults to true.
Responses
StatusContentDescription
200application/json CrawlReportBounded crawl completed
400URL is missing, malformed, or disallowed by the SSRF policy
429Per-target or anonymous request limit exceeded
502Target DNS resolution failed
Example request
curl -sS 'https://api.lifub.com/audit/crawl'

GET /audit/diff

Compare two Site Audit snapshots
Returns legacy URL/health changes plus technical SEO score and stable-check changes only when the SEO analyses are comparable. Optional legacy and SEO-delta gates are independent; format=junit opts into CI output.
Parameters
NameInTypeRequiredDescription
beforequerystring | nullno
afterquerystring | nullno
formatquerystring | nullno`?format=junit` → JUnit XML instead of JSON (CI-friendly).
gatequeryinteger | null (int32)no`?gate=N` → a CI gate: respond `422` when the score delta is below `N` (default 0 → any regression fails) or anything `broke`/`disappeared`.
seoDeltaGatequeryinteger | null (int32)no`?seoDeltaGate=N` → respond `422` when comparable technical SEO score movement is below `N`; unavailable/incomparable SEO fails closed.
Responses
StatusContentDescription
200application/json SnapshotDiff
application/xml string
Snapshot diff; JUnit XML is returned when format=junit
400Both snapshot IDs are required
404A snapshot was not found
422application/json SnapshotDiff
application/xml string
A requested legacy or SEO delta gate failed
500Snapshot storage failed
Example request
curl -sS 'https://api.lifub.com/audit/diff'

POST /audit/snapshots

Capture an immutable Site Audit snapshotcostly

Auth Optional X-Site-Audit-Voucher: <audit voucher> - Optional operator-issued Site Audit voucher UUID: adds paid reports and skips the proof of work.

Parameters
NameInTypeRequiredDescription
urlquerystring | nullno
labelquerystring | nullno
expectIndexablequeryboolean | nullnoWhether this URL is deliberately excluded from search indexing.
seoGatequeryinteger | null (int32)noOptional technical SEO score gate. Incomplete/unscored analysis fails closed and is not stored as a snapshot.
Responses
StatusContentDescription
201application/json StoredSnapshotSnapshot captured and stored
400URL is missing, malformed, or disallowed
402The supplied Site Audit voucher has no paid reports remaining
403The supplied Site Audit voucher is invalid or revoked
409Paid credits exist but are temporarily reserved by audits in progress; retry after the Retry-After interval
422application/json AuditReportThe requested technical SEO gate failed; snapshot was not stored
429Free one-hour quota, per-target, or anonymous safety limit exceeded
500Snapshot storage failed
503The durable Site Audit quota backend is unavailable
Example request
curl -sS -X POST 'https://api.lifub.com/audit/snapshots'

GET /audit/snapshots/{id}

Fetch a stored Site Audit snapshot
Parameters
NameInTypeRequiredDescription
idpathstringyesSnapshot bearer-token ID
Responses
StatusContentDescription
200application/json StoredSnapshotStored snapshot
404Snapshot not found
500Snapshot storage failed
Example request
curl -sS 'https://api.lifub.com/audit/snapshots/{id}'

test

Test endpoints

GET /test

Delayed response test endpoint
Returns a response after an optional delay. Useful for testing timeouts, latency handling, and async behavior. Accepts any HTTP method.
Parameters
NameInTypeRequiredDescription
timeoutInMsqueryinteger | null (int64)noDelay in milliseconds before responding (defaults to 0)
Responses
StatusContentDescription
200text/plain stringTriggered response after delay
Example request
curl -sS 'https://api.lifub.com/test'

stats

Build and deployment information

GET /stats.json

Build + health info
Returns api status, version, build hash, build number, and uptime. No per-client data. For per-key usage, see the authenticated metering APIs.
Responses
StatusContentDescription
200application/json StatsBuild and health info
Example request
curl -sS 'https://api.lifub.com/stats.json'

crawler

Web crawler and site indexing API

POST /sites/crawl

Handler for POST /sites/crawl (admin multi-site crawl)costly

Auth Requires Authorization: Bearer <admin secret> - The operator's admin secret.

Parameters
NameInTypeRequiredDescription
allSitesCrawlquerybooleanyesForce crawl all sites regardless of last crawl time
isThrottledquerybooleanyesApply rate limiting
clearIndexquerybooleanyesClear index before crawl
Request body — application/json required
SchemaSitesCrawlStatus
Example
{
  "sites": [
    {
      "crawled": "string",
      "pageCount": 0,
      "siteId": "00000000-0000-4000-8000-000000000000",
      "siteProfile": {
        "configs": [
          {}
        ],
        "email": "string",
        "id": "00000000-0000-4000-8000-000000000000",
        "secret": "00000000-0000-4000-8000-000000000000"
      }
    }
  ]
}
Responses
StatusContentDescription
200application/json SitesCrawlStatusMulti-site crawl completed
401Unauthorized - invalid service secret
Example request
curl -sS -X POST 'https://api.lifub.com/sites/crawl?allSitesCrawl={allSitesCrawl}&isThrottled={isThrottled}&clearIndex={clearIndex}'

GET /sites/crawl/status

Handler for GET /sites/crawl/status

Auth Requires Authorization: Bearer <admin secret> - The operator's admin secret.

Responses
StatusContentDescription
200application/json SitesCrawlStatusCrawl status retrieved
401Unauthorized - invalid service secret
Example request
curl -sS 'https://api.lifub.com/sites/crawl/status'

PUT /sites/crawl/status

Handler for PUT /sites/crawl/statuslive write

Auth Requires Authorization: Bearer <admin secret> - The operator's admin secret.

Request body — application/json required
SchemaSitesCrawlStatus
Example
{
  "sites": [
    {
      "crawled": "string",
      "pageCount": 0,
      "siteId": "00000000-0000-4000-8000-000000000000",
      "siteProfile": {
        "configs": [
          {}
        ],
        "email": "string",
        "id": "00000000-0000-4000-8000-000000000000",
        "secret": "00000000-0000-4000-8000-000000000000"
      }
    }
  ]
}
Responses
StatusContentDescription
200application/json SitesCrawlStatusCrawl status updated
401Unauthorized - invalid service secret
Example request
curl -sS -X PUT 'https://api.lifub.com/sites/crawl/status'

POST /sites/{siteId}/crawl

Handler for POST /sites/{siteId}/crawlcostly

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
urlquerystringyesURL to crawl
emailquerystringnoEmail for notifications
sitemapsOnlyquerybooleanyesOnly crawl URLs from sitemap
allowUrlWithQueryquerybooleanyesAllow URLs with query parameters
pageBodyCssSelectorquerystringyesCSS selector for page body extraction
maxPagesqueryintegernoCap pages crawled this run; clamped to the 500 throttled ceiling (only lowers it)
Responses
StatusContentDescription
200application/json CrawlerJobResultCrawl completed successfully
404Site not found or invalid credentials
500Crawl failed
Example request
curl -sS -X POST 'https://api.lifub.com/sites/{siteId}/crawl?url={url}&sitemapsOnly={sitemapsOnly}&allowUrlWithQuery={allowUrlWithQuery}&pageBodyCssSelector={pageBodyCssSelector}'

POST /sites/{siteId}/recrawl

Handler for POST /sites/{siteId}/recrawlcostly

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
clearIndexquerybooleanyesClear existing index before recrawl
Responses
StatusContentDescription
200application/json CrawlerJobResultRecrawl completed successfully
404Site not found or invalid credentials
500Recrawl failed
Example request
curl -sS -X POST 'https://api.lifub.com/sites/{siteId}/recrawl?clearIndex={clearIndex}'

sites

Site management, search, and page operations

POST /sites

POST /sites - Create a new sitelive write
Request body — application/json
Schemanull | SiteProfileCreation
Example
{
  "configs": [
    {
      "allowUrlWithQuery": false,
      "pageBodyCssSelector": "string",
      "sitemapsOnly": false,
      "url": "string"
    }
  ],
  "email": "string"
}
Responses
StatusContentDescription
201application/json SiteCreationSite created
500Internal server error
Example request
curl -sS -X POST 'https://api.lifub.com/sites'

PUT /sites/flush

PUT /sites/flush - commit the search index (make recent writes visible)live write

Auth Requires Authorization: Bearer <admin secret> - The operator's admin secret.

Responses
StatusContentDescription
204Indices flushed
400Invalid credentials
503Flush failed
Example request
curl -sS -X PUT 'https://api.lifub.com/sites/flush'

POST /sites/rss

POST /sites/rss - Create new site and index RSS feedcostly
Parameters
NameInTypeRequiredDescription
feedUrlquerystringyesRSS feed URL
stripHtmlTagsquerybooleannoStrip HTML tags from content
Responses
StatusContentDescription
200application/json SiteIndexSummaryFeed indexed
400Invalid feed URL or content
Example request
curl -sS -X POST 'https://api.lifub.com/sites/rss?feedUrl={feedUrl}'

GET /sites/{siteId}

GET /sites/{siteId} - Fetch all document IDs for a site
Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
Responses
StatusContentDescription
200application/json array<string>Document IDs
404No documents found
Example request
curl -sS 'https://api.lifub.com/sites/{siteId}'

DELETE /sites/{siteId}

DELETE /sites/{siteId} - Clear all pages for a sitelive write

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
Responses
StatusContentDescription
200Site cleared
204No content to clear
Example request
curl -sS -X DELETE 'https://api.lifub.com/sites/{siteId}'

GET /sites/{siteId}/autocomplete

GET /sites/{siteId}/autocomplete - Autocomplete suggestions
Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
queryquerystringyesPartial search query
Responses
StatusContentDescription
200application/json AutocompleteAutocomplete suggestions
400Invalid site ID
Example request
curl -sS 'https://api.lifub.com/sites/{siteId}/autocomplete?query={query}'

GET /sites/{siteId}/har

GET /sites/{siteId}/har - Export site pages as HAR 1.2
When real HAR data exists (from crawls after HAR capture was enabled), returns filtered real HTTP metadata. Falls back to synthetic HAR from page content when no real HAR data is available (backward compatibility).

Auth One of Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created. or Authorization: Bearer <admin secret> - The operator's admin secret.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
statusqueryinteger (int32)noFilter by HTTP status code
sincequerystringnoISO 8601: entries crawled at or after
beforequerystringnoISO 8601: entries crawled before
urlquerystringnoURL prefix filter
minTimequerynumber (double)noMinimum response time in ms
maxTimequerynumber (double)noMaximum response time in ms
contentTypequerystringnoMIME type filter
sortquerystringnoSort: time, status, crawledAt; prefix - for desc
limitqueryintegernoMax entries (default 100, max 10000)
offsetqueryintegernoSkip first N entries
Responses
StatusContentDescription
200application/jsonHAR 1.2 export of crawled pages
403Invalid site secret
404No pages found for site
Example request
curl -sS 'https://api.lifub.com/sites/{siteId}/har'

GET /sites/{siteId}/pages

GET /sites/{siteId}/pages - Fetch page by URL
Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
urlquerystringyesPage URL
Responses
StatusContentDescription
200application/json FetchedPagePage found
404Page not found
Example request
curl -sS 'https://api.lifub.com/sites/{siteId}/pages?url={url}'

PUT /sites/{siteId}/pages

PUT /sites/{siteId}/pages - Add/update page in site indexlive write

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
If-Matchheaderstring | nullnoOptional ETag precondition. Omit for legacy unconditional replacement; use * to require an existing page.
Request body — application/json required
SchemaSitePageInput
Example
{
  "body": "string",
  "id": "string",
  "labels": [
    "string"
  ],
  "siteId": "00000000-0000-4000-8000-000000000000",
  "thumbnail": "string",
  "title": "string",
  "updated": "string",
  "url": "string"
}
Responses
StatusContentDescription
200application/json FetchedPagePage indexed
400Malformed If-Match header or missing URL
404Invalid credentials
412If-Match did not match the current page
Example request
curl -sS -X PUT 'https://api.lifub.com/sites/{siteId}/pages'

DELETE /sites/{siteId}/pages

DELETE /sites/{siteId}/pages - Delete page by URLlive write

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
urlquerystringyesPage URL
Responses
StatusContentDescription
204Page deleted
404Page not found
Example request
curl -sS -X DELETE 'https://api.lifub.com/sites/{siteId}/pages?url={url}'

PUT /sites/{siteId}/pages/{pageId}

PUT /sites/{siteId}/pages/{pageId} - Update existing page by IDlive write

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
pageIdpathstringyesPage ID (SHA-256 hash, 64 characters)
If-Matchheaderstring | nullnoOptional ETag precondition. Omit for legacy unconditional replacement; use * to require an existing page.
Request body — application/json required
SchemaSitePageInput
Example
{
  "body": "string",
  "id": "string",
  "labels": [
    "string"
  ],
  "siteId": "00000000-0000-4000-8000-000000000000",
  "thumbnail": "string",
  "title": "string",
  "updated": "string",
  "url": "string"
}
Responses
StatusContentDescription
200application/json FetchedPagePage updated
400Invalid page ID length or missing URL
404Invalid credentials or page not found
412If-Match did not match the current page
Example request
curl -sS -X PUT 'https://api.lifub.com/sites/{siteId}/pages/{pageId}'

DELETE /sites/{siteId}/pages/{pageId}

DELETE /sites/{siteId}/pages/{pageId} - Delete page by IDlive write

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
pageIdpathstringyesPage ID
Responses
StatusContentDescription
204Page deleted
404Page not found
Example request
curl -sS -X DELETE 'https://api.lifub.com/sites/{siteId}/pages/{pageId}'

PUT /sites/{siteId}/pages/{pageId}/backdate

PUT /sites/{siteId}/pages/{pageId}/backdate?updated=<rfc3339>live write
Admin-only test affordance — see [`SiteState::backdate_page`]. Overwrites a page's `updated` timestamp so the recrawl obsolete-cleanup can be tested against a live deployment. Gated by the service/admin secret (same Bearer token as `/flush`), not a per-site secret.

Auth Requires Authorization: Bearer <admin secret> - The operator's admin secret.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
pageIdpathstringyesPage ID
updatedquerystringyesNew RFC 3339 `updated` timestamp
Responses
StatusContentDescription
204Page timestamp updated
403Invalid admin credentials
404Page not found
Example request
curl -sS -X PUT 'https://api.lifub.com/sites/{siteId}/pages/{pageId}/backdate?updated={updated}'

GET /sites/{siteId}/profile

GET /sites/{siteId}/profile - Fetch site profile

Auth One of Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created. or Authorization: Bearer <admin secret> - The operator's admin secret.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
Responses
StatusContentDescription
200application/json SiteProfileProfile found
404Profile not found
Example request
curl -sS 'https://api.lifub.com/sites/{siteId}/profile'

PUT /sites/{siteId}/profile

PUT /sites/{siteId}/profile - Update site profilelive write

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
Request body — application/json required
SchemaSiteProfileUpdate
Example
{
  "configs": [
    {
      "allowUrlWithQuery": false,
      "pageBodyCssSelector": "string",
      "sitemapsOnly": false,
      "url": "string"
    }
  ],
  "email": "string",
  "secret": "00000000-0000-4000-8000-000000000000"
}
Responses
StatusContentDescription
200application/json SiteProfileProfile updated
404Profile not found
Example request
curl -sS -X PUT 'https://api.lifub.com/sites/{siteId}/profile'

PUT /sites/{siteId}/rss

PUT /sites/{siteId}/rss - Index RSS feed into existing sitecostly

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
feedUrlquerystringyesRSS feed URL
stripHtmlTagsquerybooleannoStrip HTML tags from content
Responses
StatusContentDescription
200application/json SiteIndexSummaryFeed indexed
400Invalid feed URL or content
404Site not found or invalid credentials
Example request
curl -sS -X PUT 'https://api.lifub.com/sites/{siteId}/rss?feedUrl={feedUrl}'
GET /sites/{siteId}/search - Search within a site
Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
queryquerystringyesSearch query
Responses
StatusContentDescription
200application/json SearchResultSearch results
400Invalid site ID
Example request
curl -sS 'https://api.lifub.com/sites/{siteId}/search?query={query}'

PUT /sites/{siteId}/xml

PUT /sites/{siteId}/xml - Index generic XML feed into existing sitecostly

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
xmlUrlquerystringyesXML feed URL
stripHtmlTagsquerybooleannoStrip HTML tags from content
clearIndexquerybooleannoClear existing index before importing
Responses
StatusContentDescription
200application/json SiteIndexSummaryXML indexed
400Invalid XML URL or content
404Site not found or invalid credentials
Example request
curl -sS -X PUT 'https://api.lifub.com/sites/{siteId}/xml?xmlUrl={xmlUrl}'

email

Email notification endpoints

POST /sites/{siteId}/email/setup-info

POST /sites/{siteId}/email/setup-info — send setup information email.costly
Bearer-gated by the per-site secret. The pre-Bearer global counter that previously sat in front of this handler was removed: it incremented before credentials were checked, so any three failed requests could disable the endpoint for everyone until process restart. Authentication is the rate limit. Once auth passes, a per-site cooldown ([`EMAIL_COOLDOWN`]) prevents the endpoint from being turned into an outbound spam / mail-bomb vector for the site's owner mailbox — nobody legitimately hits "send me my setup info" more than once per minute. The cooldown is claimed *before* the send rather than after: a transport that fails slowly is exactly when a retrying client would otherwise hammer the mailbox. The address is the one stored on the site profile, never one supplied in the request, so this endpoint cannot be aimed at a third party. `email` is optional in the Getting Started Gadget, so a profile without one is a normal outcome and answers 204, not an error.

Auth Requires Authorization: Bearer <site secret> - The site's secret UUID, issued when the site was created.

Parameters
NameInTypeRequiredDescription
siteIdpathstring (uuid)yesSite ID
Responses
StatusContentDescription
200Email sent successfully
204No email address is registered for this site
404Site not found or invalid credentials
429Cooldown active — retry after Retry-After
503Email delivery is unavailable
Example request
curl -sS -X POST 'https://api.lifub.com/sites/{siteId}/email/setup-info'

pages

Direct page lookup by ID

GET /pages/{id}

GET /pages/{id} - Fetch a page by its ID.
This endpoint allows direct page lookup without requiring site credentials. The page ID is a SHA-256 hash of the siteId + URL.
Parameters
NameInTypeRequiredDescription
idpathstringyesPage ID (SHA-256 hash)
Responses
StatusContentDescription
200application/json FetchedPagePage found
404Page not found
Example request
curl -sS 'https://api.lifub.com/pages/{id}'

assets

Asset Manager (Beta — subject to change) — physical asset utilization & booking engine

GET /assets/asset/{assetId}

Public consumer view of an asset and its bookable windows
Parameters
NameInTypeRequiredDescription
assetIdpathstringyesShareable asset id
Responses
StatusContentDescription
200application/json AssetViewAsset with availability
404Unknown asset
Example request
curl -sS 'https://api.lifub.com/assets/asset/{assetId}'

GET /assets/asset/{assetId}/calendar.ics

Public availability calendar for one asset as iCalendar (.ics)
Parameters
NameInTypeRequiredDescription
assetIdpathstringyesShareable asset id
Responses
StatusContentDescription
200text/calendartext/calendar
404Unknown asset
Example request
curl -sS 'https://api.lifub.com/assets/asset/{assetId}/calendar.ics'

POST /assets/asset/{assetId}/windows/{windowId}/hold

Tentatively hold a seat that auto-releases after ttlSeconds (atomic, capacity-aware)costly
Parameters
NameInTypeRequiredDescription
assetIdpathstringyesShareable asset id
windowIdpathstringyesWindow to hold a seat on
Request body — application/json required
SchemaNewHold
Example
{
  "consumer": "string",
  "fields": {},
  "note": "string",
  "promoCode": "string",
  "quantity": 0,
  "ttlSeconds": 0
}
Responses
StatusContentDescription
201application/json ReservationHeld booking (confirm it with its id before it expires)
400Invalid ttlSeconds
404Unknown asset or window
409No seats left (slot_full)
Example request
curl -sS -X POST 'https://api.lifub.com/assets/asset/{assetId}/windows/{windowId}/hold'

POST /assets/asset/{assetId}/windows/{windowId}/reserve

Reserve/book a utilization window (atomic, capacity-aware)costly
Parameters
NameInTypeRequiredDescription
assetIdpathstringyesShareable asset id
windowIdpathstringyesWindow to reserve
Request body — application/json required
SchemaNewReservation
Example
{
  "consumer": "string",
  "fields": {},
  "note": "string",
  "promoCode": "string",
  "quantity": 0
}
Responses
StatusContentDescription
201application/json ReservationBooking receipt (id doubles as the cancel capability)
404Unknown asset or window
409No seats left (slot_full)
Example request
curl -sS -X POST 'https://api.lifub.com/assets/asset/{assetId}/windows/{windowId}/reserve'

POST /assets/asset/{assetId}/windows/{windowId}/waitlist

Join a window's waitlist; the head is auto-promoted when a seat freescostly
Parameters
NameInTypeRequiredDescription
assetIdpathstringyesShareable asset id
windowIdpathstringyesWindow to queue for
Request body — application/json required
SchemaNewWaitlist
Example
{
  "consumer": "string",
  "fields": {},
  "note": "string",
  "quantity": 0
}
Responses
StatusContentDescription
201application/json WaitlistEntryWaitlist ticket (id is the leave capability)
404Unknown asset or window
429Waitlist is full
Example request
curl -sS -X POST 'https://api.lifub.com/assets/asset/{assetId}/windows/{windowId}/waitlist'

DELETE /assets/asset/{assetId}/windows/{windowId}/waitlist/{ticketId}

Leave a window's waitlist (ticketId is the capability)live write
Parameters
NameInTypeRequiredDescription
assetIdpathstringyesShareable asset id
windowIdpathstringyesWindow the ticket is on
ticketIdpathstringyesWaitlist ticket to remove
Responses
StatusContentDescription
204Left the waitlist
404Unknown ticket
Example request
curl -sS -X DELETE 'https://api.lifub.com/assets/asset/{assetId}/windows/{windowId}/waitlist/{ticketId}'

POST /assets/owners

Mint a new ownerId (manager bearer secret) — Betacostly
Responses
StatusContentDescription
201Minted ownerId — `{ "ownerId": "<uuid>" }`
Example request
curl -sS -X POST 'https://api.lifub.com/assets/owners'

GET /assets/owners/{ownerId}/assets

List the owner's assets; ?windows=false for a lightweight summary

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
windowsquerybooleannofalse → metadata + windowCount only (no inlined windows)
Responses
StatusContentDescription
200application/json AssetRowsResponseAssets (with windows, or summary); delegated responses omit ownerId and Viewer responses omit reservation IDs
404Unknown owner
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/assets'

POST /assets/owners/{ownerId}/assets

Create an assetlive write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
Request body — application/json required
SchemaNewAsset
Example
{
  "bookingFields": [
    {
      "key": "string",
      "label": "string",
      "options": [
        "string"
      ],
      "required": false,
      "type": "text"
    }
  ],
  "currency": "string",
  "description": "string",
  "kind": "string",
  "name": "string",
  "policy": {
    "cancelCutoffMinutes": 0,
    "minNoticeMinutes": 0
  },
  "promoCodes": [
    {
      "amount": 0.0,
      "code": "string",
      "kind": "percent",
      "validFrom": 0,
      "validUntil": 0
    }
  ],
  "timezone": "string"
}
Responses
StatusContentDescription
201application/json AssetCreated asset
404Unknown owner
Example request
curl -sS -X POST 'https://api.lifub.com/assets/owners/{ownerId}/assets'

PATCH /assets/owners/{ownerId}/assets/{assetId}

Edit an asset's mutable fields in place (only provided fields change)live write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
assetIdpathstringyesAsset to edit
Request body — application/json required
SchemaUpdateAsset
Example
{
  "bookingFields": [
    {
      "key": "string",
      "label": "string",
      "options": [
        "string"
      ],
      "required": false,
      "type": "text"
    }
  ],
  "currency": "string",
  "description": "string",
  "kind": "string",
  "name": "string",
  "policy": {
    "cancelCutoffMinutes": 0,
    "minNoticeMinutes": 0
  },
  "promoCodes": [
    {
      "amount": 0.0,
      "code": "string",
      "kind": "percent",
      "validFrom": 0,
      "validUntil": 0
    }
  ],
  "timezone": "string"
}
Responses
StatusContentDescription
200application/json AssetUpdated asset
400Invalid field
404Unknown asset
Example request
curl -sS -X PATCH 'https://api.lifub.com/assets/owners/{ownerId}/assets/{assetId}'

DELETE /assets/owners/{ownerId}/assets/{assetId}

Delete an asset and all its windowslive write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
assetIdpathstringyesAsset to delete
Responses
StatusContentDescription
204Deleted
404Unknown asset
Example request
curl -sS -X DELETE 'https://api.lifub.com/assets/owners/{ownerId}/assets/{assetId}'

POST /assets/owners/{ownerId}/assets/{assetId}/availability

Bulk-generate a recurring grid of utilization windowslive write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
assetIdpathstringyesAsset the windows belong to
Request body — application/json required
SchemaNewAvailability
Example
{
  "capacity": 0,
  "cost": 0.0,
  "firstStart": 0,
  "repeatCount": 0,
  "repeatEveryMinutes": 0,
  "slotMinutes": 0,
  "slots": 0
}
Responses
StatusContentDescription
201application/json AvailabilityResultGeneration result
400Invalid recurrence
404Unknown asset
Example request
curl -sS -X POST 'https://api.lifub.com/assets/owners/{ownerId}/assets/{assetId}/availability'

GET /assets/owners/{ownerId}/assets/{assetId}/windows

A page of one asset's windows (owner drill-down)

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
assetIdpathstringyesAsset to page windows for
offsetqueryinteger (int32)noStart index (default 0)
limitqueryinteger (int32)noPage size (default 200, max 1000)
Responses
StatusContentDescription
200application/json WindowsPageResponseA page of windows; Viewer responses omit reservation IDs
404Unknown asset
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/assets/{assetId}/windows'

POST /assets/owners/{ownerId}/assets/{assetId}/windows

Add a utilization window (time slot with a cost + capacity)live write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
assetIdpathstringyesAsset the window belongs to
Request body — application/json required
SchemaNewWindow
Example
{
  "capacity": 0,
  "cost": 0.0,
  "end": 0,
  "start": 0
}
Responses
StatusContentDescription
201application/json WindowCreated window
404Unknown asset
409Window overlaps an existing one
Example request
curl -sS -X POST 'https://api.lifub.com/assets/owners/{ownerId}/assets/{assetId}/windows'

DELETE /assets/owners/{ownerId}/assets/{assetId}/windows

Bulk-delete an asset's windows whose start is in [from, to) (clear a date range)live write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
assetIdpathstringyesAsset to clear windows on
fromqueryinteger (int64)yesRange start, epoch-ms (inclusive)
toqueryinteger (int64)yesRange end, epoch-ms (exclusive)
Responses
StatusContentDescription
200application/json BulkDeleteResultCount deleted
400Missing/invalid range
404Unknown asset
Example request
curl -sS -X DELETE 'https://api.lifub.com/assets/owners/{ownerId}/assets/{assetId}/windows?from={from}&to={to}'

PATCH /assets/owners/{ownerId}/assets/{assetId}/windows/{windowId}

Edit a window's cost/capacity in place (time range immutable)live write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
assetIdpathstringyesAsset the window belongs to
windowIdpathstringyesWindow to edit
Request body — application/json required
SchemaUpdateWindow
Example
{
  "capacity": 0,
  "cost": 0.0
}
Responses
StatusContentDescription
200application/json WindowUpdated window
400Invalid cost/capacity
404Unknown asset or window
409Capacity below current bookings
Example request
curl -sS -X PATCH 'https://api.lifub.com/assets/owners/{ownerId}/assets/{assetId}/windows/{windowId}'

DELETE /assets/owners/{ownerId}/assets/{assetId}/windows/{windowId}

Delete a utilization windowlive write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
assetIdpathstringyesAsset the window belongs to
windowIdpathstringyesWindow to delete
Responses
StatusContentDescription
204Deleted
404Unknown asset or window
Example request
curl -sS -X DELETE 'https://api.lifub.com/assets/owners/{ownerId}/assets/{assetId}/windows/{windowId}'

GET /assets/owners/{ownerId}/audit

The owner's activity log (audit trail), newest first

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
Responses
StatusContentDescription
200application/json AuditRowsResponseAudit events; delegated responses omit reservationId and free-form detail
404Unknown owner
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/audit'

GET /assets/owners/{ownerId}/calendar.ics

Owner's full schedule as an iCalendar (.ics) feed

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
Responses
StatusContentDescription
200text/calendartext/calendar
404Unknown owner
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/calendar.ics'

GET /assets/owners/{ownerId}/export

Export the owner's full data snapshot (assets, bookings, members, webhooks) — owner only

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId (root)
Responses
StatusContentDescription
200application/json OwnerExportPortable snapshot
403Only the owner may export
404Unknown owner
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/export'

POST /assets/owners/{ownerId}/import

Import a data snapshot into an empty owner (ids preserved) — owner onlylive write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesA freshly-minted, empty ownerId (root)
Request body — application/json required
SchemaOwnerExport
Example
{
  "assets": [
    {
      "asset": {
        "bookingFields": [
          {}
        ],
        "createdAt": 0,
        "currency": "string",
        "description": "string",
        "id": "string",
        "kind": "string",
        "name": "string",
        "ownerId": "string",
        "policy": {},
        "promoCodes": [
          {}
        ],
        "timezone": "string"
      },
      "windowCount": 0,
      "windows": [
        {
          "assetId": "string",
          "capacity": 0,
          "cost": 0.0,
          "end": 0,
          "id": "string",
          "remaining": 0,
          "reservations": [
            {}
          ],
          "start": 0,
          "waitlisted": 0
        }
      ]
    }
  ],
  "exportedAt": 0,
  "members": [
    {
      "createdAt": 0,
      "id": "string",
      "label": "string",
      "role": "owner"
    }
  ],
  "ownerId": "string",
  "version": 0,
  "webhooks": [
    {
      "createdAt": 0,
      "events": [
        "string"
      ],
      "id": "string",
      "url": "string"
    }
  ]
}
Responses
StatusContentDescription
201application/json ImportResultImport counts
400Invalid snapshot or unsupported export version
403Only the owner may import
404Unknown owner
409Target owner is not empty or a globally keyed id collides
Example request
curl -sS -X POST 'https://api.lifub.com/assets/owners/{ownerId}/import'

GET /assets/owners/{ownerId}/members

List the owner's role-scoped member tokens (owner only)

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId (root)
Responses
StatusContentDescription
200application/json array<Member>Members
403Only the owner may list members
404Unknown owner
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/members'

POST /assets/owners/{ownerId}/members

Mint a role-scoped member token (owner only)live write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId (root)
Request body — application/json required
SchemaNewMember
Example
{
  "label": "string",
  "role": "string"
}
Responses
StatusContentDescription
201application/json MemberMinted member token (id is the bearer token + revoke handle)
400Invalid role/label
403Only the owner may mint members
404Unknown owner
429Per-owner member limit reached
Example request
curl -sS -X POST 'https://api.lifub.com/assets/owners/{ownerId}/members'

DELETE /assets/owners/{ownerId}/members/{memberId}

Revoke a member token (owner only)live write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId (root)
memberIdpathstringyesMember token to revoke
Responses
StatusContentDescription
204Revoked
403Only the owner may revoke members
404Unknown owner or member
Example request
curl -sS -X DELETE 'https://api.lifub.com/assets/owners/{ownerId}/members/{memberId}'

GET /assets/owners/{ownerId}/report

Owner utilization + revenue report (aggregation over all assets)

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
Responses
StatusContentDescription
200application/json OwnerReportAggregated report
404Unknown owner
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/report'

GET /assets/owners/{ownerId}/reservations

List active bookings across the owner's assets (paginated)

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
offsetqueryinteger (int32)noStart index (default 0)
limitqueryinteger (int32)noPage size (default 200, max 1000)
Responses
StatusContentDescription
200application/json ReservationRowsResponseReservations (page); Viewer responses omit reservation IDs
404Unknown owner
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/reservations'

POST /assets/owners/{ownerId}/reservations/{reservationId}/cancel

Owner cancels a booking on one of their assets (frees the seat)live write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
reservationIdpathstringyesBooking to cancel
Responses
StatusContentDescription
200application/json ReservationCancelled booking
404Unknown reservation
Example request
curl -sS -X POST 'https://api.lifub.com/assets/owners/{ownerId}/reservations/{reservationId}/cancel'

PATCH /assets/owners/{ownerId}/reservations/{reservationId}/modify

Owner edits a booking on one of their assets in place (seat quantity, fields, note)live write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId (or a member token)
reservationIdpathstringyesBooking to modify
Request body — application/json required
SchemaModifyReservation
Example
{
  "fields": {},
  "note": "string",
  "quantity": 0
}
Responses
StatusContentDescription
200application/json ReservationUpdated booking
400Invalid quantity/fields/note
404Unknown reservation
409Increase doesn't fit (slot_full) / booking not active
Example request
curl -sS -X PATCH 'https://api.lifub.com/assets/owners/{ownerId}/reservations/{reservationId}/modify'

GET /assets/owners/{ownerId}/webhooks

List the owner's registered outbound webhooks

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
Responses
StatusContentDescription
200application/json array<Webhook>Registered webhooks
404Unknown owner
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/webhooks'

POST /assets/owners/{ownerId}/webhooks

Register an outbound event webhook (SSRF-guarded, web ports only)live write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
Request body — application/json required
SchemaNewWebhook
Example
{
  "events": [
    "string"
  ],
  "url": "string"
}
Responses
StatusContentDescription
201application/json WebhookRegistered webhook (id doubles as the delete capability)
400Invalid or disallowed URL / event
404Unknown owner
429Per-owner webhook limit reached
Example request
curl -sS -X POST 'https://api.lifub.com/assets/owners/{ownerId}/webhooks'

DELETE /assets/owners/{ownerId}/webhooks/{webhookId}

Delete a registered webhooklive write

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesManager's secret ownerId
webhookIdpathstringyesWebhook to delete
Responses
StatusContentDescription
204Deleted
404Unknown owner or webhook
Example request
curl -sS -X DELETE 'https://api.lifub.com/assets/owners/{ownerId}/webhooks/{webhookId}'

GET /assets/owners/{ownerId}/whoami

Resolve the caller's effective role (owner or a member token)

Auth The ownerId path segment is the owner's bearer secret; treat the URL as a credential.

Parameters
NameInTypeRequiredDescription
ownerIdpathstringyesOwner secret or a member token
Responses
StatusContentDescription
200application/json WhoAmIResolved identity
404Unknown owner/member token
Example request
curl -sS 'https://api.lifub.com/assets/owners/{ownerId}/whoami'

POST /assets/reservations/{reservationId}/cancel

Consumer cancels their own booking (reservationId is the capability)costly
Parameters
NameInTypeRequiredDescription
reservationIdpathstringyesThe booking receipt id
Responses
StatusContentDescription
200application/json ReservationCancelled booking
404Unknown reservation
Example request
curl -sS -X POST 'https://api.lifub.com/assets/reservations/{reservationId}/cancel'

POST /assets/reservations/{reservationId}/confirm

Promote a held seat to a confirmed booking (reservationId is the capability)costly
Parameters
NameInTypeRequiredDescription
reservationIdpathstringyesThe held booking's receipt id
Responses
StatusContentDescription
200application/json ReservationConfirmed booking
404Unknown reservation
409Hold already expired, or the booking isn't a hold
Example request
curl -sS -X POST 'https://api.lifub.com/assets/reservations/{reservationId}/confirm'

PATCH /assets/reservations/{reservationId}/modify

Consumer edits their active booking in place (seat quantity, custom fields, note)costly
Parameters
NameInTypeRequiredDescription
reservationIdpathstringyesThe booking receipt id
Request body — application/json required
SchemaModifyReservation
Example
{
  "fields": {},
  "note": "string",
  "quantity": 0
}
Responses
StatusContentDescription
200application/json ReservationUpdated booking
400Invalid quantity/fields/note
404Unknown reservation
409Increase doesn't fit (slot_full) / booking not active
Example request
curl -sS -X PATCH 'https://api.lifub.com/assets/reservations/{reservationId}/modify'

POST /assets/reservations/{reservationId}/reschedule

Consumer reschedules their booking onto another window of the same assetcostly
Parameters
NameInTypeRequiredDescription
reservationIdpathstringyesThe booking receipt id
Request body — application/json required
SchemaRescheduleRequest
Example
{
  "windowId": "string"
}
Responses
StatusContentDescription
201application/json ReservationNew booking (fresh reservationId)
404Unknown reservation or target window
409Target full / already cancelled / same window
Example request
curl -sS -X POST 'https://api.lifub.com/assets/reservations/{reservationId}/reschedule'

visit

Visit tracking beacon, Web Vitals, engagement, and per-site analytics

GET /analytics.js

Consolidated tracking tag (server record + client collector)live write
Fetched by every client that loads the embed. Records the visit from server-side request fingerprints (IP / JA3 / JA4 / TLS cipher / HTTP protocol / UA / ASN / country) and returns a small collector script that enriches the row with client signals when executed. `siteId` is required to count the visit.
Parameters
NameInTypeRequiredDescription
siteIdquerystringnoSite id to count the visit against
queryIPquerystringnoOverride IP for the geolocation / ASN lookup
cookiesquerybooleannoOption B: enable the first-party loxal_vid cookie (returning-visitor detection)
bannerquerybooleannoOption B + banner: serve Analytics' own equal-prominence consent UI
cookieConsentquerystringnoOption B2 (`signal`): defer the cookie write until `window.loxalAnalytics.consent('granted')`
vitalsquerybooleannoOpt-in Core Web Vitals collection (LCP / INP / CLS / TTFB), posted to /visit/vitals.json on visibilitychange
engagementquerybooleannoOpt-in engagement-depth collection (time on page, scroll %, interaction count), posted to /visit/engagement.json on visibilitychange
outboundquerybooleannoOpt-in outbound-clicks & downloads collection (external <a> hosts, download file extensions), posted to /visit/outbound.json on visibilitychange
Responses
StatusContentDescription
200Collector JavaScript (text/javascript)
Example request
curl -sS 'https://api.lifub.com/analytics.js'

POST /visit.json

Visit beacon with bot / AI-agent recognitionlive write
First-party analytics beacon: the tracking tag POSTs the visitor's client runtime signals (all optional) and this fuses them with the server-side IP / ASN / header fraud analysis (shared with /inspect.json) to return a human / suspect / bot verdict for the visit. Anonymous callers are rate-limited per IP; keyed callers are metered against their plan quota — any tier may call it. A missing or malformed body still returns a server-side verdict (a beacon must never hard-fail the host page). Optional `queryIP` overrides the IP used for the geolocation / ASN lookup, mirroring /whois.json and /inspect.json.

Auth Optional X-API-Key: <API key> - Optional API key. Anonymous callers are rate-limited per IP; a key raises the limit and meters usage.

Parameters
NameInTypeRequiredDescription
queryIPquerystringnoOverride IP for the geolocation / ASN lookup
Request body — application/json required
DescriptionClient runtime signals from the tracking tag (all optional)
SchemaClientSignals
Example
{
  "hardwareConcurrency": null,
  "languagesCount": null,
  "pluginsCount": null,
  "referrer": null,
  "url": null,
  "webdriver": null
}
Responses
StatusContentDescription
200application/json VisitVisit verdict
429Rate limit or monthly quota exceeded
500Internal server error
Example request
curl -sS -X POST 'https://api.lifub.com/visit.json'

POST /visit/engagement.json

Attach engagement depth to a /analytics.js visitlive write
Third step of the /analytics.js tag (JS clients, `?engagement=true` only). POSTs the visit's visible time-on-page, deepest scroll %, and interaction count as a small JSON object; the server merges them into the visit's `meta.engagement`. Never affects the bot / human verdict.

Auth Optional X-API-Key: <API key> - Optional API key. Anonymous callers are rate-limited per IP; a key raises the limit and meters usage.

Parameters
NameInTypeRequiredDescription
idquerystringyesvisit_id issued by GET /analytics.js
Request body — application/json required
DescriptionEngagement-depth sample
SchemaPageEngagement
Example
{
  "interactions": null,
  "scrollPct": null,
  "timeOnPageMs": null
}
Responses
StatusContentDescription
200Accepted (fire-and-forget)
Example request
curl -sS -X POST 'https://api.lifub.com/visit/engagement.json?id={id}'

POST /visit/enrich.json

Enrich a /analytics.js visit with client signalslive write
Second step of the /analytics.js tag (JS clients only). POSTs client runtime signals for the `id` returned by /analytics.js; re-fuses them with the stored server verdict, updates the row, and returns the verdict as JSON.

Auth Optional X-API-Key: <API key> - Optional API key. Anonymous callers are rate-limited per IP; a key raises the limit and meters usage.

Parameters
NameInTypeRequiredDescription
idquerystringyesvisit_id issued by GET /analytics.js
Request body — application/json required
DescriptionClient runtime signals
SchemaClientSignals
Example
{
  "hardwareConcurrency": null,
  "languagesCount": null,
  "pluginsCount": null,
  "referrer": null,
  "url": null,
  "webdriver": null
}
Responses
StatusContentDescription
200Verdict (verdict / score / reasons) as JSON
Example request
curl -sS -X POST 'https://api.lifub.com/visit/enrich.json?id={id}'

POST /visit/outbound.json

Attach outbound clicks & downloads to a /analytics.js visitlive write
Fourth step of the /analytics.js tag (JS clients, `?outbound=true` only). POSTs a batch of outbound clicks / downloads for the visit (destination hosts and/or download file extensions only — never the raw href, path, query, or fragment); the server merges them into the visit's `meta.outbound`. Never affects the bot / human verdict.

Auth Optional X-API-Key: <API key> - Optional API key. Anonymous callers are rate-limited per IP; a key raises the limit and meters usage.

Parameters
NameInTypeRequiredDescription
idquerystringyesvisit_id issued by GET /analytics.js
Request body — application/json required
DescriptionBatched outbound events
SchemaPageOutbound
Example
{
  "events": [
    {
      "downloadExt": null,
      "host": null
    }
  ]
}
Responses
StatusContentDescription
200Accepted (fire-and-forget)
Example request
curl -sS -X POST 'https://api.lifub.com/visit/outbound.json?id={id}'

POST /visit/retention.json

Set a site's data-retention TTL (days) — requires siteSecretlive write

Auth Requires ?siteSecret=<site secret> - The site's secret UUID, as a query parameter.

Parameters
NameInTypeRequiredDescription
siteIdquerystringyesSite id (UUID) to configure
siteSecretquerystringyesThe site's write/config secret (UUID)
daysqueryinteger (int64)yesRetention in days (>= 1); default 365
Responses
StatusContentDescription
200Updated retention
400Missing/invalid siteId or days
401Missing or invalid siteSecret
503Metering backend temporarily unavailable — retry later (Retry-After)
Example request
curl -sS -X POST 'https://api.lifub.com/visit/retention.json?siteId={siteId}&siteSecret={siteSecret}&days={days}'

GET /visit/stats.json

Aggregated visit analytics for a site
Returns visit totals (human / suspect / bot), a per-UTC-day human/bot time series, and top-pages / top-referrers breakdowns for a tracked `siteId`, over the last `days` days (default 7, max 365). `siteId` is a required UUID — the unguessable id is itself the read capability, so no separate token is needed; a non-UUID `siteId` is rejected.

Auth Optional X-API-Key: <API key> - Optional API key. Anonymous callers are rate-limited per IP; a key raises the limit and meters usage.

Parameters
NameInTypeRequiredDescription
siteIdquerystringyesSite id (UUID) whose analytics to read
daysqueryinteger (int32)noWindow length in days (default 7, max 365)
Responses
StatusContentDescription
200application/json VisitStatsAggregated visit analytics
400Missing or non-UUID `siteId` parameter
500Internal server error
503Metering backend unavailable or report capacity busy - retry later (Retry-After: 30)
Example request
curl -sS 'https://api.lifub.com/visit/stats.json?siteId={siteId}'

POST /visit/visibility.json

Toggle a site's analytics public/private — requires siteSecretlive write

Auth Requires ?siteSecret=<site secret> - The site's secret UUID, as a query parameter.

Parameters
NameInTypeRequiredDescription
siteIdquerystringyesSite id (UUID) to configure
siteSecretquerystringyesThe site's write/config secret (UUID)
privatequerybooleanyestrue = private (stats reads need the siteSecret); false = public
Responses
StatusContentDescription
200Updated visibility
400Missing/invalid siteId or private flag
401Missing or invalid siteSecret
503Metering backend temporarily unavailable — retry later (Retry-After)
Example request
curl -sS -X POST 'https://api.lifub.com/visit/visibility.json?siteId={siteId}&siteSecret={siteSecret}&private={private}'

POST /visit/vitals.json

Attach Core Web Vitals to a /analytics.js visitlive write
Third step of the /analytics.js tag (JS clients, `?vitals=true` only). POSTs the visit's Largest Contentful Paint, Interaction to Next Paint, Cumulative Layout Shift, and Time to First Byte as a small JSON object; the server merges them into the visit's `meta.vitals`. Never affects the bot / human verdict.

Auth Optional X-API-Key: <API key> - Optional API key. Anonymous callers are rate-limited per IP; a key raises the limit and meters usage.

Parameters
NameInTypeRequiredDescription
idquerystringyesvisit_id issued by GET /analytics.js
Request body — application/json required
DescriptionCore Web Vitals sample
SchemaPageVitals
Example
{
  "cls": null,
  "inpMs": null,
  "lcpMs": null,
  "ttfbMs": null
}
Responses
StatusContentDescription
200Accepted (fire-and-forget)
Example request
curl -sS -X POST 'https://api.lifub.com/visit/vitals.json?id={id}'

admin

Operator-only crawler introspection

GET /admin/active-crawls

Handler for GET /admin/active-crawls
Returns the number of in-flight crawls plus the site IDs currently being crawled. Used by the §7.4 migration runbook to poll for `running == 0` after setting `MIGRATION_LOCKDOWN=true`.

Auth Requires Authorization: Bearer <admin secret> - The operator's admin secret.

Responses
StatusContentDescription
200application/json ActiveCrawlsResponseActive crawl counter
401Unauthorized - invalid admin secret
Example request
curl -sS 'https://api.lifub.com/admin/active-crawls'

GET /admin/promoted

Handler for GET /admin/promoted
Reports whether the embedded store's warm-standby writer has been armed (`EmbeddedStore::promote`, see the `api` Deployment `strategy:` comment in `api.yaml`). Unauthenticated by design — a pure operational liveness signal like `/health`, exposing no data. A warm-standby process answers `/health` while still read-only, before it takes the SQLite writer flock. Deployment checks can poll this endpoint so callers never race the promotion window into a raw 500 "attempt to write a readonly database" (`voyk/src/store/embedded/mod.rs::promote`).
Responses
StatusContentDescription
200Writer promoted; the store accepts writes
503Still warm-standby; retry after the given Retry-After
Example request
curl -sS 'https://api.lifub.com/admin/promoted'

Schemas

ActiveCrawlsResponse

Response shape for `GET /admin/active-crawls` (§7.4).

Properties
NameTypeRequiredDescription
runningintegeryes
site_idsarray<string (uuid)>yes

Used by: GET /admin/active-crawls

AnalyticsStatsSummary

Last successful `visit_stats` compute, published on `/stats.json` as `analyticsStats` so the `redis-capacity` CI job can page when the compute time drifts toward STATS_COMPUTE_CEILING - the growth signal that would otherwise end in a permanent 503 (the 2026-09-02 lesson: a warning nobody reads is not detection).

Properties
NameTypeRequiredDescription
daysinteger (int64)yesThe window it computed, in days.
lastComputeMsinteger (int64)yesWall-clock milliseconds of the most recent successful compute.
lastOkUnixMsinteger (int64)yesUnix milliseconds when that compute finished.
sitestringyesThe site it computed (the busiest site dominates in practice).

Asset

A published asset. `ownerId` is the manager's secret and is only returned to the root manager/export surface, never to delegated or public views.

Properties
NameTypeRequiredDescription
bookingFieldsarray<BookingFieldDef>noOwner-defined fields a consumer must/may fill when booking (empty = none).
createdAtinteger (int64)yesEpoch-ms creation time.
currencystringyesCurrency the window costs are quoted in — a free-form symbol or code (`$`, `USD`, `EUR`, `£`, `CHF`, …); **not necessarily USD**.
descriptionstring | nullno
idstringyesUnguessable UUID; doubles as the shareable consumer link.
kindstringyesFree-form category, e.g. `drone`, `court`, `vehicle`, `lesson`, `property`.
namestringyesHuman name, e.g. "Court 1", "DJI Mavic 3", "Grand-piano lesson".
ownerIdstringyesThe managing owner's secret UUID.
policynull | BookingPolicyno
promoCodesarray<PromoCode>noOwner-defined discount codes (empty = none). Only ever returned on the manager surface — never leaked on the public [`AssetView`].
timezonestringnoIANA timezone the window times are meant to be read in (e.g. `Europe/Berlin`); defaults to `UTC`. Times on the wire stay epoch-ms UTC — this only drives how a client renders them.

Used by: POST /assets/owners/{ownerId}/assets, PATCH /assets/owners/{ownerId}/assets/{assetId}

AssetRowsResponse

Role-dependent serialization for `GET /owners/{token}/assets`.

Type: array<AssetWithWindows> | array<DelegatedAssetWithWindows> | array<ViewerAssetWithWindows>

Used by: GET /assets/owners/{ownerId}/assets

AssetView

The public consumer view of an asset: descriptive fields + windows, **without** the `ownerId` and without other consumers' booking details.

Properties
NameTypeRequiredDescription
bookingFieldsarray<BookingFieldDef>noCustom fields the consumer must/may fill when booking (empty = none).
currencystringyes
descriptionstring | nullno
idstringyes
kindstringyes
namestringyes
timezonestringyesIANA timezone the consumer should read the slots in (default `UTC`).
windowsarray<PublicWindow>yes

Used by: GET /assets/asset/{assetId}

AssetWithWindows

Manager view of one asset: the asset plus all its windows (with bookings). In the lightweight summary view (`?windows=false`) `windows` is empty and only `windowCount` is populated — so listing a large catalog doesn't inline every window+booking.

Properties
NameTypeRequiredDescription
assetAssetyes
windowCountinteger (int32)noTotal windows on the asset (always set; the only window info in the summary view).
windowsarray<Window>no

AuditAction

One remediation objective backed by the checks that contributed to it.

Properties
NameTypeRequiredDescription
categorystringnoReport area associated with the highest-severity contributor.
contributingCheckIdsarray<string>noStable check or finding IDs merged into this action.
evidencearray<string>noBounded observations supporting this action.
idstringnoStable remediation-family identifier, for example `header.csp`.
recommendationstringnoConcrete remediation guidance from the highest-severity contributor.
severitystringnoHighest advisory severity among the contributing checks.
titlestringnoShort action label.

AuditCoverage

How much of the fixed one-page audit fan-out returned usable response evidence. Coverage is independent of both health scores: a complete report can faithfully describe an unhealthy origin, while consumers can use a partial report to distinguish unevaluated checks from the frozen legacy score and absence-shaped finding contract.

Properties
NameTypeRequiredDescription
auxiliaryResponseReceivedbooleannoAt least one same-origin auxiliary probe returned an HTTP response. Together with `pageResponseReceived: false`, this fact identifies differential reachability without guessing whether the differentiator was an edge policy, WAF, client fingerprint, IP, or something else.
completebooleannoEvery scheduled probe returned an HTTP response and a complete body.
completedProbesintegernoProbes that returned an HTTP response without truncation.
pageResponseReceivedbooleannoThe primary page returned an HTTP response, irrespective of its status.
totalProbesintegernoProbes scheduled for this audit.

AuditEvent

One entry in an owner's activity log (audit trail). Append-only, newest-first, capped at [`AUDIT_MAX`] and TTL'd like the rest of the owner's data.

Properties
NameTypeRequiredDescription
actionstringyesMachine-readable action: `owner.create`, `asset.create`, `asset.update`, `asset.delete`, `window.add`, `window.update`, `window.delete`, `availability.generate`, `reserve`, `hold`, `confirm`, `cancel`, `modify`, `webhook.add`, `webhook.delete`, `member.add`, `member.delete`, `waitlist.join`, `waitlist.leave`, `waitlist.promote`, `owner.import`, `window.bulk_delete`.
assetIdstring | nullno
atinteger (int64)yesEpoch-ms when the action happened.
detailstring | nullnoShort human detail (asset name, booker handle, generated counts, …).
reservationIdstring | nullno
windowIdstring | nullno

AuditProvenance

Compact provenance for a completed one-page report.

Properties
NameTypeRequiredDescription
capturedAtinteger (int64)noServer capture time in Unix epoch milliseconds.
effectiveOriginstring | nullnoEffective scheme/host/port, only when the page returned HTTP.
effectiveUrlstring | nullnoFinal URL observed after redirects, only when the page returned HTTP.
engineVersionstringnoVersion of the Site Audit engine that produced the report.
httpClientProfileIdstringnoStable request/client profile used by the fetcher.
pageEncodingnull | PageEncodingProvenanceno
reportSchemaVersioninteger (int32)noVersion of the top-level report wire schema.
sourceRevisionstring | nullnoExact deployed Git revision when a release supplied one.
vantagestring | nullnoCoarse operator-defined capture vantage, never an egress IP or hostname.

AuditReport

Top-level audit report.

Properties
NameTypeRequiredDescription
actionsarray<AuditAction>noDeterministic remediation objectives derived from existing findings and failed checks. Additive and independent of every score.
cdnCdnInfono
coverageAuditCoverageno
findingDetailsarray<FindingDetail>noThe same findings, in the same order, each carrying a stable catalogue id, a severity, and the what/why/how explanation needed to act on it (see [`explain`]). Additive: `findings` remains the established field, and a snapshot captured before this existed simply deserializes empty.
findingsarray<string>noNotable issues found, human-readable.
healthnull | HealthReportno
hoststringnoThe normalized host that was audited.
metanull | PageMetano
probesarray<ProbeResult>noPer-URL results, page first then well-known paths sorted.
provenancenull | AuditProvenanceno
scoreinteger (int32)noLegacy HTTP/security health score 0–100. Its established weighting and gate semantics are intentionally independent of the versioned SEO score.
securitynull | SecurityAnalysisno
seonull | SeoReportno
shareIdstring | nullnoSet when this report was published as a shareable copy (`?share=true`): the unguessable id it is retrievable under at `/audit/snapshots/{shareId}`. Absent means the report was not stored, so nothing about this run is retrievable by anyone else.
targetstringnoThe audited URL, normalized and with any `user:pass@` credentials stripped (never the raw input — see [`run_audit`]).
tlsnull | TlsInfono
unicodeHoststring | nullnoUnicode presentation form of an IDN host. `host` remains the canonical ASCII/Punycode security and network identity.

Used by: GET /audit.json, POST /audit/snapshots

AuditRowsResponse

Role-dependent serialization for the manager audit trail.

Type: array<AuditEvent> | array<DelegatedAuditEvent>

Used by: GET /assets/owners/{ownerId}/audit

Autocomplete

Autocomplete response. Equivalent to Kotlin `Autocomplete`.

Properties
NameTypeRequiredDescription
resultsarray<string>yes

Used by: GET /sites/{siteId}/autocomplete

AvailabilityResult

Result of a bulk `availability` generation.

Properties
NameTypeRequiredDescription
createdinteger (int32)yesWindows actually created.
skippedinteger (int32)yesCandidate windows skipped (overlapped an existing window, or hit the cap).

Used by: POST /assets/owners/{ownerId}/assets/{assetId}/availability

BookingCounts

Booking counts by (effective) lifecycle state for a [`OwnerReport`].

Properties
NameTypeRequiredDescription
cancelledinteger (int32)yes
completedinteger (int32)yes
confirmedinteger (int32)yes
expiredinteger (int32)yes
heldinteger (int32)yes

BookingFieldDef

An owner-defined field a consumer fills in at booking time — e.g. a license plate, headcount, delivery address, or a `select` from a fixed option list. Definitions live on the [`Asset`]; the consumer-supplied values live on the [`Reservation`] (`fields`). Cross-vertical and self-contained.

Properties
NameTypeRequiredDescription
keystringyesStable machine key (`[A-Za-z0-9_-]`, unique per asset) — the key under which the consumer's value is stored in `Reservation.fields`.
labelstringyesHuman-facing label the booking UI renders.
optionsarray<string>noAllowed values for a `select` field (ignored for other types).
requiredbooleannoWhether the consumer must supply a non-empty value.
typeFieldTypeno

BookingPolicy

A per-asset booking policy: how close to a slot's start a consumer may still book it, and how close they may still cancel it. `0` = no restriction; an all-zero policy is normalised to "no policy". Owner-initiated cancels always bypass the cancel cutoff.

Properties
NameTypeRequiredDescription
cancelCutoffMinutesinteger (int32)noA consumer can't cancel a booking within this many minutes of the slot start.
minNoticeMinutesinteger (int32)noA consumer can't book/hold a window starting within this many minutes of now.

One broken internal link found during the crawl.

Properties
NameTypeRequiredDescription
referencedBystringyesA page that referenced this link (the first one seen).
statusinteger | null (int32)noThe HTTP status (4xx/5xx), or `None` when the link was unreachable.
urlstringyes

BulkDeleteResult

Result of a bulk window delete.

Properties
NameTypeRequiredDescription
deletedinteger (int32)yes

Used by: DELETE /assets/owners/{ownerId}/assets/{assetId}/windows

CdnChange

A report-level CDN / edge-network transition between two snapshots. This is an *origin* property (every same-origin probe sees the same CDN), so it lives beside `score_delta` rather than in the per-URL `changes` list.

Properties
NameTypeRequiredDescription
afterstring | nullnoVendor on the AFTER snapshot.
beforestring | nullnoVendor on the BEFORE snapshot (absent when unnamed or not detected).
kindstringyes`added` (origin → now behind a CDN), `removed` (CDN no longer fingerprinted), or `changed` (different vendor, e.g. Fastly → Cloudflare).

CdnInfo

Whether — and which — CDN / edge network fronts the audited origin, inferred from response headers. Derived at fetch time and kept deliberately compact and *value-free*: the tell-tale headers (`cf-ray`, `x-amz-cf-id`, …) carry per-request-unique values, so we record only the header **names** that matched. That keeps the signal stable across re-audits (no spurious `header-changed` in the snapshot diff).

Properties
NameTypeRequiredDescription
detectedbooleannoTrue when a CDN/edge network was positively identified (a vendor signature matched, or the RFC 8586 `CDN-Loop` header was present).
evidencearray<string>noStable evidence: the response-header names that triggered the match (values omitted — see the struct docs).
vendorstring | nullnoThe identified provider (e.g. `Cloudflare`, `Fastly`, `Amazon CloudFront`) when a vendor-specific signature matched; `None` when a CDN is present but the vendor could not be named.

ChainCert

One certificate in the chain the server presented (leaf and each additional cert), summarised for the report. A compact, SSL-Labs-style chain view.

Properties
NameTypeRequiredDescription
issuerstring | nullno
keyBitsinteger | null (int32)no
keyTypestring | nullno`EC` / `RSA` / `Ed25519`.
notAfterinteger (int64)no`notAfter`, epoch seconds.
signatureAlgorithmstring | nullno
subjectstring | nullno

ClientSignals

Client-side runtime signals POSTed by the tag. Every field is optional: a *missing* field is "unknown" and is **not** penalised, only a field that is present-and-bot-like contributes to the score (so an empty `{}` beacon scores 0 on the client side and falls back to the server-side verdict).

Properties
NameTypeRequiredDescription
hardwareConcurrencynumber | null (double)no`navigator.hardwareConcurrency` — 0 is a headless tell.
languagesCountinteger | null (int32)no`navigator.languages.length` — headless contexts often report 0.
pluginsCountinteger | null (int32)no`navigator.plugins.length` — 0 on a non-mobile UA is mildly suspect.
referrerstring | nullno`document.referrer` — the visit's traffic source. Reduced to its host and stored for the top-referrers rollup; never used for scoring.
urlstring | nullno`location.href` of the tracked page — used only to drop same-site (internal-navigation) referrers from that rollup.
webdriverboolean | nullno`navigator.webdriver` — true under automation (Selenium/Puppeteer/…).

Used by: POST /visit.json, POST /visit/enrich.json

CountRow

One row of a "top N" breakdown — a path (top pages) or a referrer host (top referrers) with its visitor / view split over the window.

Properties
NameTypeRequiredDescription
botinteger (int64)yes
humaninteger (int64)yes
labelstringyesThe path (top pages) or referrer host (top referrers).
uniqueVisitorsinteger (int64)yesDistinct visitors (by client IP) — the sort key for the breakdown.
visitsinteger (int64)yesPage views (total beacons) for this label.

CrawlCoverage

Honest accounting for the bounded discovery and link sample.

Properties
NameTypeRequiredDescription
homepageLinksDiscoveredintegerno
homepageLinksTruncatedbooleanno
incompletePagesintegerno
internalLinksCheckedintegerno
internalLinksDiscoveredintegerno
linkCheckLimitReachedbooleanno
pageLimitReachedbooleanno
pagesAuditedintegerno
pagesDiscoveredintegerno
pagesSelectedintegerno
pagesWithTruncatedLinksintegerno
redirectBudgetExhaustedbooleannoAt least one otherwise-safe same-host redirect was not followed because its additional request could not be admitted under the target budget.
requestedLimitintegerno
siteSearchTruncatedbooleanno
siteSearchUrlsDiscoveredintegerno
sitemapByteLimitReachedbooleanno
sitemapBytesReadintegernoRaw response bytes retained across sitemap attempts, including error responses; this is resource accounting, not valid sitemap XML size.
sitemapDocumentReferencesRejectedintegernoInvalid, out-of-scope, too-deep, or over-queue-limit document references rejected before admission. These are not discovered/skipped documents.
sitemapDocumentsAttemptedintegernoAdmitted document candidates for which an HTTP request was attempted.
sitemapDocumentsByteLimitedintegerno
sitemapDocumentsDiscoveredintegernoDistinct normalized, in-scope sitemap document candidates admitted to the traversal queue, including the two conventional optional probes.
sitemapDocumentsFailedintegernoTransport failures and every non-2xx status except a 404 from an optional conventional probe. This can overlap `fetched` for HTTP failures.
sitemapDocumentsFetchedintegernoAttempts that received an HTTP response, regardless of its status.
sitemapDocumentsInvalidintegernoFully received responses whose XML/content is malformed or unsupported.
sitemapDocumentsSkippedintegernoAdmitted document candidates left unattempted when a traversal cap was reached. Together with `attempted`, this partitions `discovered`.
sitemapDocumentsStreamInterruptedintegerno
sitemapDocumentsTruncatedintegernoResponses not read to EOF, partitioned by the next two counters.
sitemapEntriesSeenintegerno
sitemapEntryLimitReachedbooleanno
sitemapExistsbooleanno
sitemapFetchLimitReachedbooleanno
sitemapLastmodFutureintegerno
sitemapLastmodInvalidintegerno
sitemapQueueLimitReachedbooleanno
sitemapUrlsDuplicateintegerno
sitemapUrlsInvalidintegerno
sitemapUrlsOutOfScopeintegerno
sitemapUrlsValidintegerno

CrawlDiagnostic

Stable, sampled crawl-level diagnostic. Evidence is sorted, deduplicated, and capped so a link farm or malformed sitemap cannot inflate the report.

Properties
NameTypeRequiredDescription
evidencearray<string>no
idstringno
messagestringno
sampledbooleanno
severitystringno

CrawlDiagnostics

Why a crawl indexed the page count it did. Returned on [`CrawlerJobResult::diagnostics`]. Every counter is the number of URLs that ended in that outcome; `indexed` equals [`CrawlerJobResult::page_count`]. `summary` is a human-readable explanation, most useful when `indexed` is zero or every indexed page had no extractable text (a client-side-rendered site).

Properties
NameTypeRequiredDescription
depthFilteredinteger (int64)noURLs excluded because they sit deeper than the crawl's max depth (link-distance from the seed). The depth cap is an internal crawl bound, so a large count means the site's structure is deeper than the crawler follows — same invisible-drop family as `scope_filtered`. Additive field; `#[serde(default)]` keeps pre-field payloads parseable.
emptyBodyinteger (int64)yesSubset of `indexed` whose extracted body text was empty — the signal of a client-side-rendered (JavaScript SPA) page that will not produce useful search results.
fetchErrorinteger (int64)yesURLs that never produced a usable HTTP response (DNS, TLS, connection, timeout, or an empty/unreadable body).
fetchedinteger (int64)yesURLs that reached a fetch attempt and produced a terminal outcome (everything below except `robotsBlocked` and `scopeFiltered`, which are excluded before any HTTP request is made).
frontierDroppedinteger (int64)noCandidate URLs shed because the crawl frontier hit its internal bound (`FRONTIER_MAX`) — only plausible on extremely link-dense hosts. Sheds are candidates that were never evaluated, not pages that failed. Additive field; `#[serde(default)]` keeps pre-field payloads parseable.
httpErrorinteger (int64)yesFetched URLs that returned a non-2xx HTTP status (often 403 bot protection or a 5xx).
indexErrorinteger (int64)yesPages fetched successfully but rejected by the search backend on write — a server-side error, not a problem with the crawled site. `indexed` (and `pageCount`) already exclude these, so the response stays truthful about what the store actually holds.
indexedinteger (int64)yesPages stored in the index. Equals `CrawlerJobResult.pageCount`.
nonHtmlinteger (int64)yes2xx responses whose content-type was not indexable HTML/PDF.
robotsBlockedinteger (int64)yesURLs excluded before fetch because robots.txt disallows the crawler user-agent (`opty`).
samplesarray<DiagnosticSample>yes
scopeFilteredinteger (int64)noURLs excluded before fetch because they fall outside the crawl scope: the crawler only follows URLs under the SEED URL'S PATH PREFIX (`should_crawl_url`), plus extension/query filters. A large count next to a tiny `indexed` is the signature of a too-narrow seed — e.g. an article URL instead of the host root (build #2391, where this drop was invisible and cost a live bisect to find). `#[serde(default)]` keeps payloads from pre-field builds parseable.
seedRedirectedTostring | nullnoSet when the seed URL redirected and the crawl rebased its scope onto the redirect's final scheme/host/port (`engine::rebase_scope`) — the operator gave one URL and the crawl ran against another, which qualification needs to know. Additive field; `#[serde(default)]` keeps pre-field payloads parseable and `None` stays off the wire.
summarystringyes

CrawlPage

One audited page in a crawl.

Properties
NameTypeRequiredDescription
classstringyes`present` / `redirected` / `error` / `unreachable`.
completebooleannoWhether the response body reached EOF without hitting the byte cap or a stream error. `false` for unreachable pages and legacy deserializations.
contentTypestring | nullno
finalUrlstring | nullno
linkCountintegeryesSame-host links harvested from this page (for the link check).
linksTruncatedbooleannoTrue when more same-host links existed than the per-page crawl cap.
pageEncodingnull | PageEncodingProvenanceno
provenancearray<DiscoverySource>noEvery bounded discovery source which named this URL.
seonull | SeoReportno
sizeinteger | nullno
statusinteger | null (int32)no
titlestring | nullno
urlstringyes

CrawlReport

Full crawl report.

Properties
NameTypeRequiredDescription
brokenLinksarray<BrokenLink>no
completebooleannoTrue when every operation in the selected bounded sample completed and no discovery/link resource limit was reached. This never means whole-web coverage; [`sampled`] remains true.
coverageCrawlCoverageno
diagnosticsarray<CrawlDiagnostic>no
discoveredViastringyesHow the page set was discovered: `sitemap` / `sitemap-index` / `homepage-links` / `site-search`.
findingsarray<string>no
hoststringyes
pagesarray<CrawlPage>yes
pagesAuditedintegeryes
pagesDiscoveredintegeryesDistinct pages discovered before the `limit` cap.
sampledbooleanno
scoreinteger (int32)yesAggregate health: percent of audited pages returning a healthy 2xx/3xx.
seoCrawlSeoSummaryno
targetstringyes
unicodeHoststring | nullnoUnicode presentation form of an IDN host. `host` remains canonical ASCII/Punycode for network and security decisions.

Used by: GET /audit/crawl

CrawlSeoSummary

Mean SEO score over scoreable sampled pages, kept separate from the legacy crawl health score.

Properties
NameTypeRequiredDescription
completebooleanno
meanScoreinteger | null (int32)no
pagesCompleteintegerno
pagesScoredintegerno
pagesWithAnalysisintegerno
scoreModelVersioninteger | null (int32)no

CrawlStatus

Crawl status for a site. Equivalent to Kotlin `CrawlStatus`.

Properties
NameTypeRequiredDescription
crawledstringyes
pageCountinteger (int64)no
siteIdstring | null (uuid)no
siteProfilenull | SiteProfileno

CrawlerJobResult

Result of a crawl job. Equivalent to Kotlin `CrawlerJobResult`.

Properties
NameTypeRequiredDescription
diagnosticsCrawlDiagnosticsno
pageCountintegeryes
urlsarray<string>yes

Used by: POST /sites/{siteId}/crawl, POST /sites/{siteId}/recrawl

CurlResponse

Properties
NameTypeRequiredDescription
bodystringyesResponse body content
errornull | FetchErrorno
headersmap<string, string>yesLower-cased response headers. Repeated values are comma-joined.
statusCodeinteger (int32)yesHTTP status code from the fetched URL
urlstringyesThe URL that was fetched

Used by: GET /curl.json

DelegatedAsset

An asset returned to an operational Dispatcher. The root `ownerId` bearer is intentionally absent; operational configuration stays available.

Properties
NameTypeRequiredDescription
bookingFieldsarray<BookingFieldDef>no
createdAtinteger (int64)yes
currencystringyes
descriptionstring | nullno
idstringyes
kindstringyes
namestringyes
policynull | BookingPolicyno
promoCodesarray<PromoCode>no
timezonestringyes

DelegatedAssetWithWindows

Properties
NameTypeRequiredDescription
assetDelegatedAssetyes
windowCountinteger (int32)yes
windowsarray<Window>yes

DelegatedAuditEvent

A delegated audit row. Reservation/waitlist IDs are public mutation capabilities and detail is free-form, potentially credential-bearing text, so neither crosses this non-root boundary. Dispatchers get their booking capabilities from the operational asset/window/reservation surfaces.

Properties
NameTypeRequiredDescription
actionstringyes
assetIdstring | nullno
atinteger (int64)yes
windowIdstring | nullno

DiagnosticSample

One concrete failing-URL example carried in [`CrawlDiagnostics::samples`]. A bounded list of examples so an operator sees *which* URLs failed and *why*, not just a count. `detail` carries the HTTP status text or content-type when the reason has one.

Properties
NameTypeRequiredDescription
detailstring | nullno
reasonstringyesReason tag, matching a `CrawlDiagnostics` counter: one of `httpError`, `nonHtml`, `fetchError`, `robotsBlocked`, `scopeFiltered`, `depthFiltered`, `indexError`. (Frontier sheds carry no samples — they are counted in bulk, never per URL.)
urlstringyes

DiscountKind

How a [`PromoCode`] reduces a window's per-unit cost.

Type: string one of "percent", "fixed"

DiscoverySource

How a URL entered the bounded crawl sample.

Type: string one of "target", "sitemap", "homepage-link", "site-search"

Echo

JSON payload for the echo endpoint. Mirrors the schema of `fsly::echo::Echo`, plus api-specific request-derived bot/fraud signals.

Properties
NameTypeRequiredDescription
datastringyesRequest body data (empty for GET requests)
fingerprintstringyesSHA-256 fingerprint derived from selected headers
fraudnumber (float)yesFraud score (0.0 = trusted, 1.0 = highly suspicious). Combines cheap header/UA/JA3-JA4 heuristics with the IP/TLS reputation feeds (datacenter, VPN, Spamhaus DROP, abuse.ch SSLBL JA3).
headersmap<string, array<string>>yesRequest headers (sorted alphabetically)
hoststringyesHost header value
iCloudPrivateRelaybooleanyesTrue if IP is in iCloud Private Relay range
ipstringyesClient IP address
ja3stringyesJA3 TLS fingerprint hash (if available from upstream proxy)
methodstringyesHTTP method (GET, POST, PUT, DELETE, etc.)
querymap<string, array<string>>yesParsed query parameters
schemestringyesURL scheme (http or https)
sessionstringyesSession hash (fingerprint + IP + cookie)
torExitNodebooleanyesTrue if IP is a known Tor exit node
uristringyesRequest URI path
versionstringyesHTTP version

Used by: GET /echo.json

EncodingResponse

Response payload for `/encoding.json`. Field names are camelCase to match the `/...json` API conventions.

Properties
NameTypeRequiredDescription
base64DecodedstringyesBase64-decoded input as text. Empty when the input is not valid Base64 **or** when it decodes to bytes that are not text in the applied charset — read `base64Valid` and `base64DecodedBytes` to tell those two cases apart.
base64DecodedBytesintegeryesNumber of bytes the input decoded to; `0` when `base64Valid` is false. A non-zero count with an empty `base64Decoded` means the input is valid Base64 carrying binary data rather than text.
base64EncodedstringyesBase64-encoded representation of raw bytes, standard alphabet (RFC 4648 section 4), padded with `=`.
base64UrlEncodedstringyesBase64-encoded representation of raw bytes, URL- and filename-safe alphabet (RFC 4648 section 5: `-` and `_` for `+` and `/`) with padding omitted — the form JWT/JOSE (RFC 7515) mandates.
base64ValidbooleanyesWhether the input value parses as Base64 at all. The decoder accepts both the standard and the URL-safe alphabet and tolerates missing padding, so this is `true` for far more inputs than it looks — any four-character run of alphabet characters decodes to three bytes.
binarystringyesRaw bytes in base 2, eight digits per byte, space separated. Unsigned, unlike the signed `octal` / `decimal` / `hex` views above, because a negative binary byte is not a thing anyone means by "text to binary".
byteLengthintegeryesByte count of the raw input under the applied charset. Differs from `rawLength` for any non-ASCII input (`ä` is one character, two bytes).
charsetstringyesApplied charset (may differ from requested if unsupported)
decimalstringyesDecimal representation of raw bytes
hashinteger (int64)yesFNV-1a 64-bit hash of the raw input. A non-cryptographic checksum for bucketing and change detection — deliberately named in the docs so callers can reproduce it, and deliberately not a substitute for the SHA-256 above.
hexstringyesHexadecimal representation of raw bytes
hexDecodedstringyesThe input read *as* hex and decoded back to text. Tolerates whitespace, a leading `0x`, and `:` or `-` separators. Empty when the input is not hex or does not decode to text — read `hexValid` and `hexDecodedBytes` to tell those apart, exactly as with Base64.
hexDecodedBytesintegeryesNumber of bytes the input decoded to when read as hex; `0` when `hexValid` is false.
hexEncodedstringyesRaw bytes as one contiguous lowercase hex string. This is the form `xxd -p`, checksums and wire dumps use — distinct from the bracketed, signed, Kotlin-style `hex` field above.
hexValidbooleanyesWhether the input parses as hex. Requires an even number of hex digits after separators are removed.
htmlDecodedstringyesHTML character references in the input resolved back to text. Covers every numeric reference (`&#169;`, `&#xA9;`) plus the HTML 4.01 Latin-1, special and common symbol named entities. An unrecognised entity is left exactly as it stands rather than dropped, so decoding never silently loses text.
htmlEncodedstringyesHTML-escaped input: `&`, `<`, `>`, `"` and `'` become character references, so the value is safe to interpolate into element text or a quoted attribute. `'` uses the numeric `&#39;` rather than `&apos;`, which HTML 4 does not define.
jsonEscapedstringyesThe input as a complete, quoted JSON string literal, ASCII-safe: every non-ASCII character is written as `\uXXXX` (surrogate pairs above U+FFFF). Paste-ready into JSON, JavaScript, Java or any other format that takes JSON string syntax — the surrounding quotes are included.
jsonUnescapedstringyesThe input read *as* a JSON string literal, with `\n`, `\t`, `\uXXXX` and friends resolved. The surrounding quotes are optional. Empty when the input is not a well-formed JSON string; see `jsonValid`.
jsonValidbooleanyesWhether the input parses as a JSON string literal.
md5stringyesMD5 hash as lowercase hex
octalstringyesOctal representation of raw bytes
rawstringyesOriginal input value
rawLengthintegeryesCharacter count of raw input (Unicode scalar values, not UTF-16 units)
sha1stringyesSHA-1 hash as lowercase hex
sha256stringyesSHA-256 hash as lowercase hex
urlDecodedstringyesURL/percent-decoded input
urlEncodedstringyesURL/percent-encoded representation

Used by: GET /encoding.json

EngagementAggregate

Aggregate engagement depth over a window of visits — returned as `VisitStats.engagement` and rendered on the dashboard as a KPI row. Answers *"how sticky was this site?"* with three ROBUST numbers (medians rather than means, so a couple of tab-open-for-hours outliers don't skew) plus one marketing KPI (`engaged_rate_pct`).

Properties
NameTypeRequiredDescription
engagedRatePctnumber | null (double)noMarketing KPI — share of samples that count as "engaged": time on page ≥ 10 s, OR scroll depth ≥ 50 %, OR at least one interaction. In percent (0..=100).
p50Interactionsinteger | null (int32)noMedian interaction count per visit (0..3 for the three counters: click, keydown, pointerdown).
p50ScrollPctnumber | null (double)noMedian deepest scroll depth reached, as a percentage of document height (0..=100).
p50TimeOnPageMsinteger | null (int64)noMedian visible time on page (ms) across all samples in the window. Client-side already capped at 30 min, so no outlier truncation here.
p75TimeOnPageMsinteger | null (int64)noP75 visible time on page (ms) — surfaces the tail (how long the stickiest quartile actually stayed).
samplesinteger (int64)yesTotal number of visits in the window that contributed at least one engagement field. When zero, all fields are `None`.

EntropyResponse

Mirrors the Kotlin `Entropy` DTO shape, but served as `/entropy.json`. Kotlin source (for reference): - uuid: UUID.randomUUID() - secureRandomLong: Random().nextLong() - secureRandomFloat: Random().nextFloat() - secureRandomGaussian: Random().nextGaussian() - secureRandomInt: Random().nextInt() - timestamp: Instant.now() Rust note: - We intentionally do *not* use "secure" entropy sources; we stick to a fast RNG suitable for non-cryptographic "jitter" / uniqueness. - Timestamp is ISO 8601 (RFC 3339) in UTC, which is the natural JSON representation in Rust. (Kotlin/Java `Instant` also serializes as an ISO timestamp under typical Jackson configs.)

Properties
NameTypeRequiredDescription
secure_random_floatnumber (float)yesRandom float in range [0.0, 1.0)
secure_random_gaussiannumber (double)yesRandom Gaussian-distributed double (mean=0, stddev=1)
secure_random_intinteger (int32)yesRandom 32-bit signed integer
secure_random_longinteger (int64)yesRandom 64-bit signed integer
timestampstringyesCurrent timestamp as epoch milliseconds
uuidstringyesRandom UUID v4

Used by: GET /entropy.json

FetchError

Structured MCP fetch failure. HTTP responses, including 4xx/5xx, are not failures and remain [`CurlResponse`] values. This type is reserved for validation, SSRF, transport, and bounded-body failures.

Properties
NameTypeRequiredDescription
codestringyes
kindFetchErrorKindyes
messagestringyes
retryAfterstring | nullno
statusinteger | null (int32)no

FetchErrorKind

JSON response payload (mirrors Kotlin `Curl`).

Type: string one of "invalid_request", "ssrf", "dns", "tls", "timeout", "rate_limited", "body_cap", "upstream", "transport"

FetchedPage

Response DTO for fetched/indexed pages. Matches the Kotlin `FetchedPage` structure.

Properties
NameTypeRequiredDescription
bodystring | nullno
idstring | nullno
labelsarray<string>no
siteIdstring | null (uuid)no
thumbnailstring | nullno
titlestring | nullno
updatedstring | nullno
urlstring | nullno

Used by: GET /pages/{id}, GET /sites/{siteId}/pages, PUT /sites/{siteId}/pages, PUT /sites/{siteId}/pages/{pageId}

FieldType

The value shape of an owner-defined [`BookingFieldDef`]. `Select` constrains the value to one of the field's `options`; the rest are free-form text / numeric / boolean values validated on booking.

Type: string one of "text", "number", "bool", "select"

FindingDetail

A finding plus everything a reader needs to act on it.

Properties
NameTypeRequiredDescription
categorystringnoReport section this belongs to.
docsstring | nullnoPrimary specification or reference, when one exists.
howstringnoHow to address it.
idstringnoStable catalogue id, e.g. `header.csp.missing`. Safe to match on.
messagestringnoThe same sentence that appears in `findings`.
severitystringnoAdvisory triage — independent of the frozen 0–100 score.
titlestringnoShort label for the finding kind.
whatstringnoWhat the thing being reported actually is.
whystringnoWhy it matters — the concrete consequence, not a platitude.

FoundPage

Found page in search results. Equivalent to Kotlin `FoundPage`.

Properties
NameTypeRequiredDescription
bodystring | nullno
labelsarray<string>no
thumbnailstring | nullno
titlestring | nullno
urlRawstring | nullno

HarExportQuery

Query parameters for the HAR export endpoint. All parameters are optional. When none are provided, returns all HAR entries (up to the default limit).

Properties
NameTypeRequiredDescription
beforestring | nullnoISO 8601 datetime: only include entries crawled before this time
contentTypestring | nullnoFilter by MIME type (e.g. "text/html")
limitinteger | nullnoMaximum entries to return (default: 100, max: 10000)
maxTimenumber | null (double)noMaximum response time in milliseconds
minTimenumber | null (double)noMinimum response time in milliseconds
offsetinteger | nullnoNumber of entries to skip (default: 0)
sincestring | nullnoISO 8601 datetime: only include entries crawled at or after this time
sortstring | nullnoSort field: "time", "status", "crawledAt" (default); prefix with "-" for descending
statusinteger | null (int32)noFilter by HTTP status code (e.g. 200, 404)
urlstring | nullnoURL prefix match (e.g. "https://example.com/blog")

HealthCheck

One stable weighted health check. `weight` is the model's nominal weight; only pass/fail checks contribute to `evaluatedWeight`.

Properties
NameTypeRequiredDescription
idstringno
reasonstringno
statusHealthCheckStatusno
weightinteger (int32)no

HealthCheckStatus

Type: string one of "pass", "fail", "notApplicable", "unknown"

HealthEvaluationState

Type: string one of "complete", "partial", "notEvaluated"

HealthReport

Versioned successor to the frozen top-level health score.

Properties
NameTypeRequiredDescription
checksarray<HealthCheck>no
evaluatedWeightinteger (int32)no
evaluationReasonsarray<string>no
evaluationStateHealthEvaluationStateno
scoreinteger | null (int32)no
scoreModelVersioninteger (int32)no

ImportResult

Counts of what an `import` wrote.

Properties
NameTypeRequiredDescription
assetsinteger (int32)yes
membersinteger (int32)yes
reservationsinteger (int32)yes
webhooksinteger (int32)yes
windowsinteger (int32)yes

Used by: POST /assets/owners/{ownerId}/import

Inspection

Response payload for `/inspect.json`.

Properties
NameTypeRequiredDescription
echoEchoyes
whoisWhoisyes

Used by: GET /inspect.json

Member

A role-scoped delegated credential for an owner's team. `id` is the member's bearer token — it is used *in place of the ownerId* in the manager URL and is also the revoke handle. Additive to the single-bearer model: the root ownerId keeps working and is the only credential that can mint/revoke members.

Properties
NameTypeRequiredDescription
createdAtinteger (int64)yes
idstringyes
labelstringyes
roleRoleyes

Used by: GET /assets/owners/{ownerId}/members, POST /assets/owners/{ownerId}/members

ModifyReservation

JSON body to modify an existing active booking in place — change the seat `quantity`, replace the custom `fields`, and/or edit the `note`. Every field is optional (omit to leave unchanged; a `note` of `""` clears it). A quantity increase is capacity-checked (may 409 `slot_full`); a decrease frees seats and may auto-promote a waitlister. Cost/currency/promo stay as booked.

Properties
NameTypeRequiredDescription
fieldsmap<string, string>no
notestring | nullno
quantityinteger | null (int32)no

Used by: PATCH /assets/owners/{ownerId}/reservations/{reservationId}/modify, PATCH /assets/reservations/{reservationId}/modify

NewAsset

JSON body to create an asset (the server mints `id`/`ownerId`/`createdAt`).

Properties
NameTypeRequiredDescription
bookingFieldsarray<BookingFieldDef>noOwner-defined fields a consumer fills at booking (empty = none).
currencystringno
descriptionstring | nullno
kindstringno
namestringyes
policynull | BookingPolicyno
promoCodesarray<PromoCode>noOwner-defined discount codes (empty = none).
timezonestringnoIANA timezone the slots are read in (default `UTC`).

Used by: POST /assets/owners/{ownerId}/assets

NewAvailability

JSON body to bulk-generate a recurring grid of windows. Timezone-free: all times are absolute epoch-ms. Example — eight 1-hour slots each day for a week: `{firstStart, slotMinutes:60, slots:8, repeatEveryMinutes:1440, repeatCount:7, cost, capacity}`.

Properties
NameTypeRequiredDescription
capacityinteger (int32)no
costnumber (double)yes
firstStartinteger (int64)yesEpoch-ms start of the first slot.
repeatCountinteger (int32)noNumber of repetitions (0/1 = the block once).
repeatEveryMinutesinteger (int32)noOffset between repetitions, minutes (0 = no repetition — a single block).
slotMinutesinteger (int32)yesLength of each slot, minutes.
slotsinteger (int32)yesNumber of back-to-back slots per repetition.

Used by: POST /assets/owners/{ownerId}/assets/{assetId}/availability

NewHold

JSON body to place a **tentative hold** on a seat. Like [`NewReservation`] but the booking auto-releases after `ttlSeconds` unless promoted with `confirm`.

Properties
NameTypeRequiredDescription
consumerstringyes
fieldsmap<string, string>noValues for the asset's custom booking fields, keyed by field `key`.
notestring | nullno
promoCodestring | nullnoOptional discount code (case-insensitive).
quantityinteger (int32)noUnits/seats to hold in this one call (default 1).
ttlSecondsinteger (int64)yesSeconds the hold lives before it auto-releases the seat (`HOLD_TTL_MIN_SECS`..=`HOLD_TTL_MAX_SECS`).

Used by: POST /assets/asset/{assetId}/windows/{windowId}/hold

NewMember

JSON body to mint a member token (server mints `id`/`createdAt`).

Properties
NameTypeRequiredDescription
labelstringnoOptional human label (defaults to the role name).
rolestringyes`dispatcher` (read + book + schedule) or `viewer` (read-only).

Used by: POST /assets/owners/{ownerId}/members

NewReservation

JSON body a consumer POSTs to book a window.

Properties
NameTypeRequiredDescription
consumerstringyes
fieldsmap<string, string>noValues for the asset's custom booking fields, keyed by field `key`.
notestring | nullno
promoCodestring | nullnoOptional discount code (case-insensitive); applies the asset's matching [`PromoCode`] to the snapshotted per-unit cost.
quantityinteger (int32)noUnits/seats to reserve in this one call (default 1; must fit the window's remaining capacity and not exceed its total capacity).

Used by: POST /assets/asset/{assetId}/windows/{windowId}/reserve

NewWaitlist

JSON body to join a window's waitlist.

Properties
NameTypeRequiredDescription
consumerstringyes
fieldsmap<string, string>noCustom booking-field values to carry onto the promoted hold.
notestring | nullno
quantityinteger (int32)noSeats wanted (default 1; must not exceed the window's total capacity).

Used by: POST /assets/asset/{assetId}/windows/{windowId}/waitlist

NewWebhook

JSON body to register a webhook (server mints `id`/`createdAt`, sanitizes `url`).

Properties
NameTypeRequiredDescription
eventsarray<string>noEmpty ⇒ subscribe to everything (`["*"]`).
urlstringyes

Used by: POST /assets/owners/{ownerId}/webhooks

NewWindow

JSON body to add a single utilization window (server mints `id`).

Properties
NameTypeRequiredDescription
capacityinteger (int32)noHow many can book this slot; defaults to 1.
costnumber (double)yes
endinteger (int64)yes
startinteger (int64)yes

Used by: POST /assets/owners/{ownerId}/assets/{assetId}/windows

OutboundAggregate

Aggregate outbound clicks & downloads over a window of visits — returned as `VisitStats.outbound` and rendered on the dashboard as two side-by-side TOP-N panels. Answers *"which off-site destinations do my visitors care about, and what are they downloading?"* with counted-frequency tables. Rows are per-*event* (not per-visit): a single visit that clicked three outbound links contributes three to `total_outbound_clicks` and can appear three times in `top_hosts`.

Properties
NameTypeRequiredDescription
samplesinteger (int64)yesTotal number of visits in the window that opted into `?outbound=true` (whether or not they actually clicked anything).
topDownloadsarray<OutboundCountRow>yesTop download file extensions by event count (descending), capped at [`TOP_N`]. Empty when no outbound event carried a download extension.
topHostsarray<OutboundCountRow>yesTop external destination hosts by event count (descending), capped at [`TOP_N`]. Empty when no outbound event carried a host.
totalDownloadsinteger (int64)yesTotal download events (i.e. events with a `downloadExt` set) in the window. Same double-counting note as `total_outbound_clicks`.
totalOutboundClicksinteger (int64)yesTotal outbound-click events (i.e. events with a `host` set) in the window. A download from an external host contributes to both this total *and* `total_downloads`.

OutboundCountRow

One row of a TOP-N outbound breakdown: a label (external destination host, or a lowercased download file extension) with the count of matching events in the window. Simpler than [`CountRow`] because the outbound aggregation is per-event (not per-visit), so unique-visitor and human/bot splits don't apply — an outbound click is either recorded or not.

Properties
NameTypeRequiredDescription
countinteger (int64)yesCount of matching events in the window.
labelstringyesExternal destination host (e.g. `github.com`) or download file extension (e.g. `pdf`), lowercased.

OutboundEvent

One outbound-click sample: an anchor click that pointed to another host (`host` set), or downloaded a known file type (`download_ext` set), or both. Emitted zero-or-more times per visit; batched into [`PageOutbound::events`] and beaconed at visit end. Never contains the raw href — only the destination host (used for TOP-N-hosts breakdowns) and/or the lowercased file extension (used for TOP-N-downloads breakdowns). URL paths, query strings, and fragments are dropped in the browser before the beacon.

Properties
NameTypeRequiredDescription
downloadExtstring | nullnoThe download file extension (lowercased, no dot; e.g. `pdf`) when the href path ends with a known download extension; `None` for a bare external-navigation click.
hoststring | nullnoThe destination host (e.g. `github.com`) when the click leaves the current site; `None` for same-host downloads. Lowercased.

OwnerExport

A complete, portable snapshot of an owner's data — assets (with their windows and bookings), members, and webhooks. The operator owns this: back it up, or re-`import` it into a fresh owner on another self-hosted instance (ids are preserved, so share links and booking receipts keep working). Audit log and waitlist queues (transient) are intentionally excluded.

Properties
NameTypeRequiredDescription
assetsarray<AssetWithWindows>no
exportedAtinteger (int64)yes
membersarray<Member>no
ownerIdstringyes
versioninteger (int32)yes
webhooksarray<Webhook>no

Used by: GET /assets/owners/{ownerId}/export, POST /assets/owners/{ownerId}/import

OwnerReport

An owner utilization + revenue report — pure aggregation over the owner's assets/windows/reservations (no new storage). Snapshot at `generatedAt`.

Properties
NameTypeRequiredDescription
assetsinteger (int32)yesNumber of assets the owner publishes.
bookingsBookingCountsyes
generatedAtinteger (int64)yesEpoch-ms the report was computed.
pastinteger (int32)yesActive bookings whose window has already ended.
revenueByCurrencymap<string, number (double)>yesRealized/committed revenue (confirmed + completed) per currency; tentative holds are excluded.
seatsBookedinteger (int64)yesSeats consumed by active (non-cancelled, non-expired) bookings.
seatsTotalinteger (int64)yesSum of window capacities (total bookable seats).
upcominginteger (int32)yesActive bookings whose window starts in the future.
utilizationPctnumber (double)yes`seatsBooked / seatsTotal * 100`, or `0` when there are no seats.
windowsinteger (int32)yesNumber of utilization windows across all assets.

Used by: GET /assets/owners/{ownerId}/report

PageEncodingProvenance

Character-decoding evidence for a fetched HTML page. Raw byte size and hash remain byte-based; this only describes the decoded view used by analyzers.

Properties
NameTypeRequiredDescription
encodingstringnoCanonical WHATWG encoding name, for example `UTF-8` or `windows-1251`.
hadErrorsbooleannoWhether malformed byte sequences required replacement characters.
sourcestringno`bom`, `httpHeader`, `htmlMeta`, `xmlDeclaration`, or `fallback`.

PageEngagement

Engagement-depth sample for one visit, opt-in via `/analytics.js?engagement=true`. A cheap "how sticky was this pageview" complement to Core Web Vitals — the three fields answer *were they still there, how far did they read, did they touch anything?* No DOM contents, no URLs, no ids; only structural counters that describe the visit's shape. All fields optional so partial reports (tab closed pre-interaction, JS scroll disabled, etc.) still record.

Properties
NameTypeRequiredDescription
interactionsinteger | null (int32)noCount of primary interactions during the visit: `click`, `keydown` (excluding modifier-only), and `pointerdown`. Deduped per-event-type so keyboard-repeat doesn't inflate the number.
scrollPctnumber | null (double)noDeepest scroll reached during the visit, as a percentage of document height (0..=100). `scrollY + innerHeight` over `scrollHeight`, ratcheted upward — never decreases on scroll-back.
timeOnPageMsinteger | null (int64)noWall-clock time the tab was foregrounded, ms — sum of visible spans between `visibilitychange`s. Capped at 30 min client-side to bound the aggregation tail (a tab left open overnight is not engagement).

Used by: POST /visit/engagement.json

PageMeta

SEO / correctness facts extracted from the page's HTML `<head>` + body. Presence-and-shape only — no scoring here; the report folds the notable gaps into `findings`.

Properties
NameTypeRequiredDescription
canonicalstring | nullno`<link rel=canonical>` href.
charsetDeclaredbooleannoThe document declares its character encoding in the markup: a `<meta charset>` or a `<meta http-equiv=content-type>` whose content carries `charset=`. Pages relying on the Content-Type header alone are still fine — [`quality_findings`] only flags the case where neither declares one.
descriptionstring | nullno`<meta name=description>` content.
h1CountintegernoCount of `<h1>` elements (0 or >1 are both SEO smells).
jsonLdbooleannoAt least one `<script type=application/ld+json>` structured-data block.
langstring | nullno`<html lang>` value.
mixedContentarray<string>no`http://` sub-resource URLs referenced from an `https://` page — active mixed content the browser will block or downgrade the padlock over. Capped to a handful for report compactness.
ogImagebooleannoOpen Graph image (`og:image`) present.
ogTitlebooleannoOpen Graph title (`og:title`) present.
robotsstring | nullno`<meta name=robots>` content (e.g. `noindex,nofollow`).
titlestring | nullno
viewportbooleannoA responsive `<meta name=viewport>` is present.

PageOutbound

Outbound-click / download batch for one visit, opt-in via `/analytics.js?outbound=true`. Beaconed once at `visibilitychange -> hidden` / `pagehide` (same pattern as vitals and engagement) so a single flush captures the whole visit. `events` may be empty — the collector still beacons at least once so the aggregation counts the visit as "opted in but had zero outbound events", distinguishing it from a site that never opted in at all.

Properties
NameTypeRequiredDescription
eventsarray<OutboundEvent>noZero-or-more outbound events accumulated during the visit.

Used by: POST /visit/outbound.json

PageVitals

Core Web Vitals sample for one visit, opt-in via `/analytics.js?vitals=true`. The four fields are Google's field-metric quartet (LCP + INP + CLS + TTFB); all are optional because a visit may report a partial set — e.g. the tab is closed before INP fires, or the page has zero interactions. Absent values are simply dropped from the aggregate; no imputation. Numeric ranges follow Google's own definitions: LCP / INP / TTFB in milliseconds; CLS unitless. Only structural / performance data; **no** DOM contents, no URLs, no ids.

Properties
NameTypeRequiredDescription
clsnumber | null (double)noCumulative Layout Shift (unitless, typically 0..1).
inpMsnumber | null (double)noInteraction to Next Paint (ms) — worst input-response latency in the session. Absent on visits with zero real interactions.
lcpMsnumber | null (double)noLargest Contentful Paint (ms) — main-content load time.
ttfbMsnumber | null (double)noTime to First Byte (ms) — network + server first-byte time from `performance.getEntriesByType('navigation')[0].responseStart`.

Used by: POST /visit/vitals.json

ProbeResult

One probed URL's result.

Properties
NameTypeRequiredDescription
classstringnoClassification: `present` / `missing` (404) / `redirected` (3xx) / `soft-404` (200 + HTML where a non-HTML well-known file is expected) / `stale` (a 2xx security.txt past its RFC 9116 `Expires` timestamp) / `error` (4xx–5xx) / `unreachable` (no response).
contentTypestring | nullno
errorstring | nullnoSet instead of a status when the probe could not complete.
errorKindstring | nullnoStable, additive classification for a transport failure. When no response arrived, the legacy `error: "fetch_error"` value remains unchanged for compatibility. A failure after response headers instead retains the status, sets `truncated`, and records its kind here.
finalUrlstring | nullnoThe URL the probe actually landed on, set **only** when the client followed one or more redirects and the final URL differs from the requested one. Surfaces the redirect chain's endpoint (e.g. an `http→https` upgrade or a `www` canonicalisation) that a status-only view hides once the hops are auto-followed.
headersmap<string, string>noCurated response headers (lower-cased keys; see [`REPORTED_HEADERS`]).
okbooleannoTrue when a response arrived with a 2xx/3xx status.
pathstringnoLogical label: `(page)` for the target, else the well-known path.
sha256string | nullno
sizeinteger | nullno
statusinteger | null (int32)no
truncatedbooleannoTrue if the body hit [`MAX_PROBE_BYTES`] or its stream was interrupted; in either case the reported hash and size cover only the captured prefix.
urlstringnoThe absolute URL probed.

PromoCode

An owner-defined discount code a consumer supplies at booking. Unlimited-use within an optional validity window; the discounted per-unit price is snapshotted onto the [`Reservation`] (`cost`) and the code recorded (`appliedCode`). (Per-code usage caps are a deferred additive extension.)

Properties
NameTypeRequiredDescription
amountnumber (double)yesPercentage (`0..=100`) for `percent`, or a flat amount for `fixed`.
codestringyesCase-insensitive code the consumer types (stored upper-cased).
kindDiscountKindno
validFrominteger | null (int64)noOptional epoch-ms lower bound the code becomes valid (inclusive).
validUntilinteger | null (int64)noOptional epoch-ms upper bound the code stays valid (exclusive).

PublicWindow

A window as seen by a *consumer*: availability only — never a booker's details.

Properties
NameTypeRequiredDescription
capacityinteger (int32)yes
costnumber (double)yes
endinteger (int64)yes
idstringyes
remaininginteger (int32)yes
reservedbooleanyesConvenience flag: `remaining == 0`.
startinteger (int64)yes
waitlistedinteger (int32)yesHow many consumers are queued on this window's waitlist.

RescheduleRequest

JSON body to reschedule a booking onto a different window of the same asset.

Properties
NameTypeRequiredDescription
windowIdstringyes

Used by: POST /assets/reservations/{reservationId}/reschedule

Reservation

A consumer's booking of a window. `id` doubles as the booker's capability to cancel/reschedule it. Snapshots cost + currency at booking time.

Properties
NameTypeRequiredDescription
appliedCodestring | nullnoThe promo code applied to this booking, if any (upper-cased).
assetIdstringyes
consumerstringyesWho booked — a free-form name or email the consumer supplies.
costnumber (double)yesPer-unit price snapshotted at booking time — **already discounted** if an `appliedCode` promo was used. Booking total = `cost * quantity`.
currencystringyes
fieldsmap<string, string>noConsumer-supplied values for the asset's [`BookingFieldDef`]s (empty = none).
holdExpiresAtinteger | null (int64)noWhen a `Held` booking auto-releases (epoch-ms UTC). Set only while the reservation is a live hold; cleared once confirmed.
idstringyesBooking receipt id (UUID) — also the consumer's cancel/reschedule capability.
notestring | nullno
quantityinteger (int32)noHow many capacity units this booking consumes (group/quantity booking). Defaults to 1; `cost` is the per-unit window cost (total = `cost * quantity`).
reservedAtinteger (int64)yes
statusReservationStatusno
windowIdstringyes

Used by: POST /assets/asset/{assetId}/windows/{windowId}/hold, POST /assets/asset/{assetId}/windows/{windowId}/reserve, POST /assets/owners/{ownerId}/reservations/{reservationId}/cancel, PATCH /assets/owners/{ownerId}/reservations/{reservationId}/modify, POST /assets/reservations/{reservationId}/cancel, POST /assets/reservations/{reservationId}/confirm, PATCH /assets/reservations/{reservationId}/modify, POST /assets/reservations/{reservationId}/reschedule

ReservationRow

One row of the manager's booking list — a reservation with asset/window context.

Properties
NameTypeRequiredDescription
assetIdstringyes
assetNamestringyes
endinteger (int64)yes
reservationReservationyes
startinteger (int64)yes
windowIdstringyes

ReservationRowsResponse

Role-dependent serialization for the manager reservation list.

Type: array<ReservationRow> | array<ViewerReservationRow>

Used by: GET /assets/owners/{ownerId}/reservations

ReservationStatus

The lifecycle state of a booking. `Confirmed`, `Completed` and (an unexpired) `Held` consume capacity; `Cancelled` and `Expired` free it. `Held` is a *tentative* reservation minted by [`AssetStore::hold`] that auto-releases at its `holdExpiresAt` — promoted to `Confirmed` by [`AssetStore::confirm`], or swept to `Expired` when its deadline passes. (Delivery-specific states — dispatched/delivered/failed — are a planned additive extension of this enum.)

Type: string one of "confirmed", "cancelled", "completed", "held", "expired"

Role

The effective authority a manager-surface caller holds. The root `ownerId` resolves to [`Role::Owner`] (full control); a minted member token resolves to its scoped role. Only `Dispatcher`/`Viewer` are mintable as members.

Type: string one of "owner", "dispatcher", "viewer"

SearchResult

Search result DTO. Equivalent to Kotlin `Result`.

Properties
NameTypeRequiredDescription
querystringyes
resultsarray<FoundPage>yes

Used by: GET /sites/{siteId}/search

SecurityAnalysis

Beyond mere presence: the *quality* of a page's security headers. A header can be present yet toothless (a `Content-Security-Policy` allowing `unsafe-inline`, an `HSTS` with a one-second `max-age`), which a presence-only check scores identically to a strong one. This captures the distinctions that matter and rolls them into a letter [`grade`].

Properties
NameTypeRequiredDescription
cspbooleanno`Content-Security-Policy` present at all.
cspStyleUnsafeInlinebooleannoThe effective STYLE policy (`style-src`, else `default-src`) permits `unsafe-inline` — reported separately from the script-level loosening because inline styles are a far smaller exposure than inline scripts.
cspUnsafeEvalbooleanno`CSP` permits `unsafe-eval`.
cspUnsafeInlinebooleanno`CSP` permits `unsafe-inline` (a common XSS-defeating loosening).
cspWildcardbooleanno`CSP` uses a wildcard source (`*`) in a fetch directive.
gradestringnoA..F letter grade over the weighted quality checks below.
hstsbooleanno`Strict-Transport-Security` present at all.
hstsIncludeSubdomainsbooleanno`HSTS` carries `includeSubDomains`.
hstsMaxAgeinteger (int64)no`HSTS` `max-age` in seconds (0 when absent/unparseable).
httpsbooleannoWhether the audited target was reached over HTTPS.
notesarray<string>noHuman-readable quality issues (folded into the report findings).
permissionsPolicybooleanno`Permissions-Policy` (feature policy) present.
referrerPolicybooleanno`Referrer-Policy` present.
xContentTypeOptionsbooleanno`X-Content-Type-Options: nosniff`.
xFrameOptionsbooleanno`X-Frame-Options` present (clickjacking defense; CSP `frame-ancestors` is the modern form but XFO is still widely honoured).

SeoAnalysisMode

Type: string one of "source-html"

SeoCategory

Type: string one of "indexability", "searchPresentation", "siteLinkHygiene", "structuredData", "content", "socialPreview", "other"

SeoCheck

One stable, machine-comparable technical SEO check.

Properties
NameTypeRequiredDescription
categorySeoCategoryno
evidencearray<string>no
idstringno
messagestringno
recommendationstringno
severitySeoSeverityno
statusSeoCheckStatusno

SeoCheckChange

One stable technical SEO check's transition between comparable snapshots.

Properties
NameTypeRequiredDescription
afterStatusnull | SeoCheckStatusno
beforeStatusnull | SeoCheckStatusno
idstringyes
kindstringyes`introduced` / `resolved` / `status-changed`.

SeoCheckStatus

Type: string one of "pass", "fail", "warning", "opportunity", "notApplicable"

SeoIndexPresence

Actual presence in a public search engine's index. Source-HTML Site Audit has no authenticated engine evidence, so it must never infer this value from HTTP, robots, canonical, or sitemap signals.

Type: string one of "notMeasured"

SeoIndexability

Technical indexing eligibility inferred from fetched page signals. In particular, `Indexable` does not mean a search engine has indexed the URL.

Type: string one of "indexable", "excluded", "blocked", "unknown"

SeoMetrics

Advisory source-content metrics. None of these counts is a ranking factor or directly changes the technical score.

Properties
NameTypeRequiredDescription
emptyAnchorCountintegerno
emptyHeadingCountintegerno
externalLinkCountintegerno
hasBylinebooleanno
headingCountintegerno
headingOrderSkipsintegerno
hreflangCountintegerno
imageCountintegerno
imagesMissingAltintegerno
internalLinkCountintegerno
paragraphCountintegerno
visibleDateCountintegerno
wordCountintegerno

SeoReport

Separately versioned technical search-readiness result. This is not a ranking score and never alters the legacy `AuditReport.score`.

Properties
NameTypeRequiredDescription
analysisModeSeoAnalysisModeno
checksarray<SeoCheck>no
completebooleanno
expectedIndexablebooleanno
indexPresenceSeoIndexPresenceno
indexabilitySeoIndexabilityno
metricsSeoMetricsno
scoreinteger | null (int32)no
scoreModelVersioninteger (int32)no
sitemapSeoSitemapSummaryno
socialPreviewSocialPreviewSummaryno
structuredDataStructuredDataSummaryno

SeoSeverity

Type: string one of "high", "medium", "low", "info"

SeoSitemapSummary

Compact, additive sitemap evidence for the audited page. This describes the already-fetched conventional sitemap documents only; it does not claim a whole-site crawl when an index or another declared sitemap was not fetched.

Properties
NameTypeRequiredDescription
completebooleanno
documentsEvaluatedintegerno
entriesEvaluatedintegerno
membershipSitemapMembershipno

SiteConfig

Site configuration for crawling. Equivalent to Kotlin `SiteProfile.Config`.

Properties
NameTypeRequiredDescription
allowUrlWithQuerybooleanno
pageBodyCssSelectorstringno
sitemapsOnlybooleanno
urlstring | nullnoURL for the site configuration. Serializes as empty string if None.

SiteCreation

Site creation response. Equivalent to Kotlin `SiteCreation`.

Properties
NameTypeRequiredDescription
siteIdstring (uuid)yes
siteSecretstring (uuid)yes

Used by: POST /sites

SiteIndexSummary

Site index summary response. Equivalent to Kotlin `SiteIndexSummary`.

Properties
NameTypeRequiredDescription
documentsarray<string>yes
failedarray<string>yes
siteIdstring (uuid)yes
siteSecretstring (uuid)yes
successCountintegeryes

Used by: POST /sites/rss, PUT /sites/{siteId}/rss, PUT /sites/{siteId}/xml

SitePage

Page content for search indexing. Equivalent to Kotlin `SitePage`.

Properties
NameTypeRequiredDescription
bodystringyes
labelsarray<string>no
thumbnailstringyes
titlestringyes
urlstringyes

SitePageInput

Input DTO for page upsert (PUT /sites/{siteId}/pages). Matches the Kotlin `SitePage` input structure with optional fields.

Properties
NameTypeRequiredDescription
bodystring | nullno
idstring | nullno
labelsarray<string>no
siteIdstring | null (uuid)no
thumbnailstring | nullno
titlestring | nullno
updatedstring | nullno
urlstring | nullno

Used by: PUT /sites/{siteId}/pages, PUT /sites/{siteId}/pages/{pageId}

SiteProfile

Site profile configuration. Equivalent to Kotlin `SiteProfile`.

Properties
NameTypeRequiredDescription
configsarray<SiteConfig>no
emailstringno
idstring (uuid)yes
secretstring (uuid)yes

Used by: GET /sites/{siteId}/profile, PUT /sites/{siteId}/profile

SiteProfileConfigInput

Site profile config input (matching Kotlin's SiteProfile.Config for input)

Properties
NameTypeRequiredDescription
allowUrlWithQuerybooleanno
pageBodyCssSelectorstringno
sitemapsOnlybooleanno
urlstringno

SiteProfileCreation

Site profile creation request.

Properties
NameTypeRequiredDescription
configsarray<SiteProfileConfigInput>no
emailstringno

Used by: POST /sites

SiteProfileUpdate

Site profile update request. Matches Kotlin `SiteProfileUpdate` which includes secret for updating credentials.

Properties
NameTypeRequiredDescription
configsarray<SiteProfileConfigInput>no
emailstring | nullno
secretstring | null (uuid)noNew secret for the site (can be used to rotate credentials)

Used by: PUT /sites/{siteId}/profile

SitemapMembership

Whether the audited page's effective URL was observed in the bounded sitemap evidence available to the one-page audit. `Unknown` is deliberately distinct from `Absent`: only a complete, valid sitemap corpus can prove absence.

Type: string one of "present", "absent", "duplicate", "unknown"

SitesCrawlStatus

Collection of crawl statuses. Equivalent to Kotlin `SitesCrawlStatus`.

Properties
NameTypeRequiredDescription
sitesarray<CrawlStatus>yes

Used by: POST /sites/crawl, GET /sites/crawl/status, PUT /sites/crawl/status

SnapshotDiff

Properties
NameTypeRequiredDescription
afterSnapshotMetayes
beforeSnapshotMetayes
cdnChangenull | CdnChangeno
changesarray<UrlChange>yes
scoreDeltainteger (int32)yes`after.score - before.score`.
seoCheckChangesarray<SeoCheckChange>noStable-ID SEO check transitions. `None` means the snapshots' SEO analyses are not comparable; `Some([])` means they are comparable and no check status changed.
seoScoreDeltainteger | null (int32)no`after.seoScore - before.seoScore`, only for complete scored reports using the same SEO model, expectation, and analysis mode. Old or incomparable snapshots omit it.
unchangedintegeryesCommon paths whose probe was byte-for-byte unchanged.

Used by: GET /audit/diff

SnapshotMeta

Properties
NameTypeRequiredDescription
capturedAtinteger (int64)yes
hoststringyes
idstringyes
labelstring | nullno
scoreinteger (int32)yes
seoScoreinteger | null (int32)noTechnical SEO score, when this snapshot contains a complete scored SEO analysis. Kept separate from the frozen compatibility score.
seoScoreModelVersioninteger | null (int32)no
targetstringyes

SocialPreviewSummary

Properties
NameTypeRequiredDescription
openGraphDescriptionbooleanno
openGraphImagebooleanno
openGraphTitlebooleanno
openGraphUrlbooleanno
twitterCardbooleanno
twitterDescriptionbooleanno
twitterImagebooleanno
twitterTitlebooleanno

Stats

Properties
NameTypeRequiredDescription
analyticsStatsnull | AnalyticsStatsSummaryno
buildNumberstringyesCI build number injected via `BUILD_NUMBER` env var at runtime. CODEX Directive 3 contract: field is named `buildNumber` in public JSON. Pinned by `api.hurl:77`.
retentionSweepnull | SweepSummaryno
scmHashstringyesSource-control hash (git commit) injected via `SCM_HASH` env var at runtime. CODEX Directive 3 contract: this field is named `scmHash` in the public JSON. Do not rename. `api.hurl:78` pins this as a published API promise.
statusstringyesService status — always `"ok"` if this endpoint responds.
uptimeSinteger (int64)yesProcess uptime in seconds since api started.
versionstringyesCrate version (from Cargo manifest).

Used by: GET /stats.json

StoredSnapshot

An immutable, labelled audit snapshot.

Properties
NameTypeRequiredDescription
capturedAtinteger (int64)yesCapture time, epoch milliseconds.
idstringyes
labelstring | nullno
reportAuditReportyes

Used by: POST /audit/snapshots, GET /audit/snapshots/{id}

StructuredDataSummary

Properties
NameTypeRequiredDescription
articleItemsintegerno
articleOpportunitiesarray<string>noMissing recommended Article-family properties, surfaced as opportunities.
articleWarningsarray<string>noMalformed values for present Article-family properties. Missing recommended properties are tracked separately as advisory opportunities.
blocksintegerno
breadcrumbIssuesarray<string>noObjective BreadcrumbList structure errors (required properties/order).
breadcrumbListsintegerno
commerceOpportunitiesarray<string>noMissing context-dependent rich-result property groups. Organization has no universally required group and therefore creates no omission issue.
commerceWarningsarray<string>noMalformed values on present Product, SoftwareApplication, Organization, or Offer properties. These remain advisory so adding the validation does not alter the versioned score model.
invalidBlocksintegerno
invalidBreadcrumbListsintegerno
itemsintegerno
offerItemsintegerno
offerVisibilityWarningsarray<string>noMaterial Offer literals observed only in strongly hidden source nodes.
organizationItemsintegerno
pageUrlSignalsTruncatedbooleannoTrue when more distinct current-page URL signals were present than can be retained safely. The parent report is then incomplete and unscored.
productItemsintegerno
softwareApplicationItemsintegerno
truncatedbooleannoTrue when the bounded JSON-LD walk reached its nesting ceiling. The parent SEO report is then incomplete and deliberately unscored.
typesarray<string>no
validBlocksintegerno

SweepSummary

Published on `/stats.json` as `retentionSweep` so the `redis-capacity` alarm can tell a stalled sweep - the only thing bounding visit growth - from a healthy one.

Properties
NameTypeRequiredDescription
errorstring | nullno
lastOkUnixMsinteger | null (int64)no
lastRunUnixMsinteger (int64)yes
purgedintegeryes
sitesFailedintegeryes
truncatedbooleanyes

TlsInfo

TLS handshake + leaf-certificate facts for an HTTPS origin, captured by a dedicated pinned handshake (see [`crate::audit::tls`]). A tractable subset of what SSL Labs reports — enough for QA (expiry, key strength, hostname match, protocol support) without the heavy machinery (full cipher enumeration, per-client handshake simulation, active vulnerability probes) tracked in the backlog. Deliberately value-stable (no per-connection nonces) so re-audits diff cleanly. Absent for `http://` targets or when the handshake failed.

Properties
NameTypeRequiredDescription
chainarray<ChainCert>noThe additional certificates beyond the leaf (intermediates / roots).
chainLengthintegernoNumber of certificates the server presented (leaf + intermediates).
cipherSuitestring | nullnoThe negotiated cipher suite (e.g. `TLS13_AES_256_GCM_SHA384`).
configurationGradestring | nullnoProtocol/configuration grade independent of certificate trust, hostname, and validity. `grade` remains the legacy combined result.
daysRemaininginteger (int64)noWhole days until `notAfter` (negative once expired), from capture time.
gradestring | nullnoA conservative TLS-config letter grade (A..F) over the subset we test — NOT the full Qualys rating (which needs cipher/legacy-protocol/vuln probing; see the backlog).
hostMatchboolean | nullnoWhether the leaf covers the audited hostname (SAN, wildcard-aware).
issuerstring | nullnoIssuer common-name / organization, best-effort.
keyBitsinteger | null (int32)noLeaf public-key strength in bits (RSA modulus / EC field size).
keyTypestring | nullnoLeaf public-key type: `EC` / `RSA` / `Ed25519`.
notAfterinteger (int64)noThe leaf certificate's `notAfter`, epoch **seconds**.
notBeforeinteger (int64)noThe leaf certificate's `notBefore`, epoch **seconds**.
protocolstring | nullnoThe highest TLS version negotiated (e.g. `TLSv1.3`).
sansarray<string>noDNS and IP Subject Alternative Names on the leaf, rendered as strings.
signatureAlgorithmstring | nullnoLeaf certificate signature algorithm (e.g. `ecdsa-with-SHA384`).
subjectstring | nullnoSubject common-name, best-effort.
supportedProtocolsarray<string>noWhich TLS versions completed a handshake (subset of `TLSv1.2`,`TLSv1.3`).
trustStateTlsTrustStateno
validNowbooleannoTrue when the certificate is currently inside its validity window.

TlsTrustState

Whether the presented certificate chain is trusted for the audited hostname by the bundled public WebPKI roots at capture time.

Type: string one of "trusted", "untrusted", "unknown"

UpdateAsset

JSON body to edit an asset in place. Every field is optional — only the provided fields change. A `description` of `""` clears it; omitting it leaves it untouched. Ids/owner/createdAt are immutable.

Properties
NameTypeRequiredDescription
bookingFieldsarray<BookingFieldDef>noReplace the whole custom-field set (send `[]` to clear; omit to leave as-is).
currencystring | nullno
descriptionstring | nullno
kindstring | nullno
namestring | nullno
policynull | BookingPolicyno
promoCodesarray<PromoCode>noReplace the whole promo-code set (send `[]` to clear; omit to leave as-is).
timezonestring | nullno

Used by: PATCH /assets/owners/{ownerId}/assets/{assetId}

UpdateWindow

JSON body to edit a window's `cost` and/or `capacity` in place (its time range is immutable — delete + re-add, or reschedule bookings, to move a slot). `capacity` may not drop below the window's current active-booking count.

Properties
NameTypeRequiredDescription
capacityinteger | null (int32)no
costnumber | null (double)no

Used by: PATCH /assets/owners/{ownerId}/assets/{assetId}/windows/{windowId}

UrlChange

One URL's change between two snapshots.

Properties
NameTypeRequiredDescription
afterClassstring | nullno
beforeClassstring | nullno
detailsarray<string>no
kindstringyes`appeared` / `disappeared` / `broke` / `fixed` / `redirected` / `content-changed` / `header-changed` / `size-changed` / `status-changed`.
pathstringyes

User

Properties
NameTypeRequiredDescription
idinteger (int64)yesUnique user ID
usernamestringyesUsername

ViewerAsset

An asset returned to a read-only Viewer. Promo codes are anonymous discount capabilities and therefore stay on root/operational surfaces.

Properties
NameTypeRequiredDescription
bookingFieldsarray<BookingFieldDef>no
createdAtinteger (int64)yes
currencystringyes
descriptionstring | nullno
idstringyes
kindstringyes
namestringyes
policynull | BookingPolicyno
timezonestringyes

ViewerAssetWithWindows

Properties
NameTypeRequiredDescription
assetViewerAssetyes
windowCountinteger (int32)yes
windowsarray<ViewerWindow>yes

ViewerReservation

A booking visible to a read-only manager. All operational booking data is retained except the reservation bearer that can mutate the booking.

Properties
NameTypeRequiredDescription
assetIdstringyes
consumerstringyes
costnumber (double)yes
currencystringyes
fieldsmap<string, string>no
holdExpiresAtinteger | null (int64)no
notestring | nullno
quantityinteger (int32)yes
reservedAtinteger (int64)yes
statusReservationStatusyes
windowIdstringyes

ViewerReservationRow

Properties
NameTypeRequiredDescription
assetIdstringyes
assetNamestringyes
endinteger (int64)yes
reservationViewerReservationyes
startinteger (int64)yes
windowIdstringyes

ViewerWindow

A manager window whose embedded reservations cannot be used as public cancel/confirm/reschedule/modify capabilities.

Properties
NameTypeRequiredDescription
assetIdstringyes
capacityinteger (int32)yes
costnumber (double)yes
endinteger (int64)yes
idstringyes
remaininginteger (int32)yes
reservationsarray<ViewerReservation>yes
startinteger (int64)yes
waitlistedinteger (int32)yes

ViewerWindowsPage

Properties
NameTypeRequiredDescription
offsetinteger (int32)yes
totalinteger (int32)yes
windowsarray<ViewerWindow>yes

Visit

`/visit.json` verdict.

Properties
NameTypeRequiredDescription
asNamestringyesNetwork owner (ASN org) — explains datacenter / proxy bot verdicts.
asNumberinteger (int32)yes
countrystringyes
countryCodestringyes
ipstringyesClient IP used for the verdict (Envoy-derived, or `queryIP` override).
reasonsarray<string>yesHuman-readable signals behind the score.
scoreinteger (int32)yesUnified bot-likelihood, 0 (human) .. 100 (bot).
verdictstringyes`human` | `suspect` | `bot`.

Used by: POST /visit.json

VisitDay

One UTC day's counts for the time series.

Properties
NameTypeRequiredDescription
botinteger (int64)yes
daystringyes`YYYY-MM-DD` (UTC).
humaninteger (int64)yes
visitsinteger (int64)yes

VisitStats

Aggregated visit analytics for one tracked site over a window — returned by [`Metering::visit_stats`] / `GET /visit/stats.json`.

Properties
NameTypeRequiredDescription
byDayarray<VisitDay>yes
engagementnull | EngagementAggregateno
outboundnull | OutboundAggregateno
sincestringyesRFC3339 lower bound of the window.
siteIdstringyesThe tracked site id (serialized as `siteId`).
topBrowsersarray<CountRow>yesTop visitor browsers in the window, classified from the User-Agent (descending), capped at [`TOP_N`].
topCitiesarray<CountRow>yesTop visitor cities in the window (descending), capped at [`TOP_N`]; unknown (unresolved) cities are excluded.
topCountriesarray<CountRow>yesTop visitor countries (GeoIP ISO codes) in the window (descending), capped at [`TOP_N`]; unknown (unresolved) countries are excluded.
topDevicesarray<CountRow>yesTop visitor device types (Desktop / Mobile / Tablet) in the window, classified from the User-Agent (descending), capped at [`TOP_N`].
topJa3array<CountRow>yesTop TLS fingerprints (JA3) in the window — a strong automation signal. Aggregate only, with a [`JA3_MIN_VISITORS`] k-anonymity floor; empties excluded. Shown as-is (not pseudonymised) to stay threat-intel-matchable.
topNetworksarray<CountRow>yesTop visitor networks (GeoIP ASN organization) in the window (descending), capped at [`TOP_N`]; unknown (unresolved) networks are excluded.
topPagesarray<CountRow>yesMost-visited paths in the window (descending), capped at [`TOP_N`].
topReferrersarray<CountRow>yesTop external referrer hosts in the window (descending), capped at [`TOP_N`]; direct / same-site (empty-referrer) visits are excluded.
topSystemsarray<CountRow>yesTop visitor operating systems in the window, classified from the User-Agent (descending), capped at [`TOP_N`].
totalsVisitTotalsyes
vitalsnull | VitalsAggregateno

Used by: GET /visit/stats.json

VisitTotals

Verdict totals for one site over a window.

Properties
NameTypeRequiredDescription
botinteger (int64)yes
humaninteger (int64)yes
newVisitsinteger (int64)yesVisits by a first-time identified visitor (Option B `loxal_vid` cookie) in the window — the "new" side of the new-vs-returning split. Zero for cookieless (Option A) sites, which issue no visitor id.
relayinteger (int64)yesVisits via iCloud Private Relay (privacy-preserving real humans).
returningVisitsinteger (int64)yesVisits by a visitor seen before (Option B cookie) in the window — the "returning" side of the split.
suspectinteger (int64)yes
torinteger (int64)yesVisits over Tor exit nodes (anonymized) in the window.
uniqueVisitorsinteger (int64)yesDistinct visitors (by client IP, cookieless) in the window.
visitsinteger (int64)yesTotal page views — one per recorded beacon — in the window.

VitalsAggregate

Aggregate Core Web Vitals over the same window as [`VisitStats`]. Absent (`None`) when the window contains zero opt-in samples for a metric. `P75` is Google's canonical field-metric summary — it reflects the 75th-percentile experience across all sampled visits, which is what Search Console reports.

Properties
NameTypeRequiredDescription
p75Clsnumber | null (double)noP75 Cumulative Layout Shift (unitless) — Google: < 0.1 good, < 0.25 needs improvement, otherwise poor.
p75InpMsnumber | null (double)noP75 Interaction to Next Paint (ms) — Google: < 200 good, < 500 needs improvement, otherwise poor.
p75LcpMsnumber | null (double)noP75 Largest Contentful Paint (ms) — Google: < 2500 good, < 4000 needs improvement, otherwise poor.
p75TtfbMsnumber | null (double)noP75 Time to First Byte (ms) — Google (informational): < 800 good, < 1800 needs improvement.
samplesinteger (int64)yesTotal number of visits in the window that contributed at least one vitals metric. When zero, the four P75 fields are all `None`.

WaitlistEntry

A consumer's place in a window's waitlist. `id` is their capability to leave the queue. When a booking on the window is cancelled and frees a seat, the head of the queue is auto-promoted into a [hold] and a `waitlist.promote` event fires (so the operator can act on it). [hold]: AssetStore::hold

Properties
NameTypeRequiredDescription
assetIdstringyes
consumerstringyes
fieldsmap<string, string>noCustom booking-field values carried onto the promoted hold (empty = none).
idstringyes
joinedAtinteger (int64)yes
notestring | nullno
quantityinteger (int32)noSeats to promote into when this ticket reaches the head (default 1). The head is only promoted when at least this many seats free.
windowIdstringyes

Used by: POST /assets/asset/{assetId}/windows/{windowId}/waitlist

Webhook

An outbound event webhook: the owner asks us to POST a JSON payload to `url` whenever a matching event fires on their assets. `id` doubles as the delete capability. `url` is SSRF-guarded at registration *and* at send.

Properties
NameTypeRequiredDescription
createdAtinteger (int64)yes
eventsarray<string>yesEvent selectors: `"*"` (all), a category (`booking`/`asset`/`window`/ `owner`), or an exact action (`reserve`, `cancel`, `asset.create`, …).
idstringyes
urlstringyesSanitized target URL (http/https, web port, credentials stripped).

Used by: GET /assets/owners/{ownerId}/webhooks, POST /assets/owners/{ownerId}/webhooks

WhoAmI

The resolved identity behind a manager-surface token — lets a client tailor its UI to the caller's authority without leaking the owner secret.

Properties
NameTypeRequiredDescription
isOwnerbooleanyesTrue iff the caller holds the root ownerId.
roleRoleyes

Used by: GET /assets/owners/{ownerId}/whoami

Whois

Whois response schema — stable across all data-source backends. **Why all 12 fields are present on every response, even when 5 of them are not populated under the current default backend:** - The default data source is DB-IP Lite (CC BY 4.0). It carries 7 of these 12 fields with real values; the remaining 5 (`code` postal, `cityId`, `countryState`, `countryStateId`, `timeZone`) come back with the `EXTENDED_FIELD_HINT` / `EXTENDED_FIELD_HINT_ID` sentinel — telling the consumer the field is part of the schema but only populated for Pro / Enterprise customers. - The 5 hinted fields are positioned to customers as **extended fields available on request** for Pro / Enterprise tiers (see `site-kit/static/product/ip-intelligence.html`, which also hosts the folded `#pricing` section). When a paying customer triggers a MaxMind GeoIP2 license purchase, the data-source path for that key fills in those fields with real values — no schema change required, no consumer breakage. CODEX Directive 3 wins via the response shape staying constant.

Properties
NameTypeRequiredDescription
asNamestringyesAutonomous System name (ISP/organization)
asNumberinteger (int32)yesAutonomous System number
citystringyesCity name
cityIdinteger (int64)yesGeoNames city ID. **Extended field** — `-1` (`EXTENDED_FIELD_HINT_ID`) under the default backend; real positive integer on the upgrade.
codestringyesPostal/ZIP code. **Extended field** — populated for Pro / Enterprise customers with the paid MaxMind GeoIP2 backend. Default backend returns the [`EXTENDED_FIELD_HINT`] sentinel.
countrystringyesCountry name
countryCodestringyesISO 3166-1 alpha-2 country code
countryIdinteger (int64)yesGeoNames country ID
countryStatestringyesState / province ISO code (e.g. "NSW"). **Extended field** — see [`EXTENDED_FIELD_HINT`].
countryStateIdinteger (int64)yesGeoNames state / province ID. **Extended field** — see [`EXTENDED_FIELD_HINT_ID`].
ipstringyesClient IP address
timeZonestringyesIANA time zone identifier. **Extended field** — see [`EXTENDED_FIELD_HINT`].

Used by: GET /whois.json

Window

A utilization window as returned to the manager: the slot plus its live booking state (`remaining` seats + the reservations on it).

Properties
NameTypeRequiredDescription
assetIdstringyes
capacityinteger (int32)yesHow many can book this slot (default 1).
costnumber (double)yesCost to use the asset for this window, in the asset's `currency`.
endinteger (int64)yesSlot end, epoch-ms UTC (exclusive); must be `> start`.
idstringyes
remaininginteger (int32)yesSeats still available = `capacity` − active bookings.
reservationsarray<Reservation>yesThe bookings on this window (active + cancelled; `status` distinguishes).
startinteger (int64)yesSlot start, epoch-ms UTC (inclusive).
waitlistedinteger (int32)noHow many consumers are queued on this window's waitlist.

Used by: POST /assets/owners/{ownerId}/assets/{assetId}/windows, PATCH /assets/owners/{ownerId}/assets/{assetId}/windows/{windowId}

WindowsPage

A page of an asset's windows (owner drill-down; `total` is the full count).

Properties
NameTypeRequiredDescription
offsetinteger (int32)yes
totalinteger (int32)yes
windowsarray<Window>yes

WindowsPageResponse

Role-dependent serialization for one asset's window page. Dispatchers need reservation IDs for their `Book` capability; viewers do not receive them.

Type: WindowsPage | ViewerWindowsPage

Used by: GET /assets/owners/{ownerId}/assets/{assetId}/windows