ReplyNodes
Qué hace este MCP
Scrapes and searches websites and structured sources including app stores, Hacker News, brand profiles, reviews, permissions, and public app metadata.
Herramientas
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'id': {'type': 'string', 'description': 'Numeric track id; provide this or appId.'}, 'lang': {'type': 'string', 'description': 'Language code.'}, 'appId': {'type': 'string', 'description': 'Bundle id; provide this or id.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'ratings': {'type': 'boolean', 'description': 'Include ratings information.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'One specific page URL to extract font information from instead of the whole domain.'}, 'domain': {'type': 'string', 'description': 'Website domain to extract font information from, e.g. replynodes.com. One of domain or url is required.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['domain'], 'properties': {'domain': {'type': 'string', 'description': 'Company domain to resolve, e.g. stripe.com. A bare domain only: no scheme, port, path, or credentials.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'One specific page URL to extract brand details from instead of the whole domain.'}, 'domain': {'type': 'string', 'description': 'Company domain to resolve, e.g. apple.com. One of domain or url is required.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['query'], 'properties': {'limit': {'type': 'integer', 'maximum': 10, 'minimum': 1, 'description': 'Maximum number of matches to return (default and maximum 10).'}, 'query': {'type': 'string', 'description': 'Brand name or domain to search for.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'One specific page URL to extract a styleguide from instead of the whole domain.'}, 'domain': {'type': 'string', 'description': 'Website domain to extract a styleguide from, e.g. replynodes.com. One of domain or url is required.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['devId'], 'properties': {'lang': {'type': 'string', 'description': 'Language code.'}, 'devId': {'type': 'string', 'description': 'public developer identifier.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'Google Play package id (e.g. com.nike.snkrs).'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'Google Play package id (e.g. com.nike.snkrs).'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}, 'countries': {'type': 'string', 'description': 'Comma-separated storefront codes.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['category'], 'properties': {'limit': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..50 are rejected.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'category': {'type': 'string', 'description': 'Google Play category identifier (e.g. GAME, SHOPPING).'}, 'language': {'type': 'string', 'description': 'Language code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'Google Play package id (e.g. com.nike.snkrs).'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['dev_id'], 'properties': {'limit': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..50 are rejected.'}, 'dev_id': {'type': 'string', 'description': 'Google Play developer id.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'Google Play package id (e.g. com.nike.snkrs).'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'Google Play package id (e.g. com.nike.snkrs).'}, 'sort': {'type': 'integer', 'description': 'Upstream review sort order.'}, 'limit': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..50 are rejected.'}, 'score': {'type': 'integer', 'maximum': 5, 'minimum': 1, 'description': 'Filter reviews to this star rating (1..5).'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}, 'next_token': {'type': 'string', 'description': 'Pagination token from a previous reviews response.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['term'], 'properties': {'term': {'type': 'string', 'description': 'Search term.'}, 'limit': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..50 are rejected.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'Google Play package id (e.g. com.nike.snkrs).'}, 'limit': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..50 are rejected.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['term'], 'properties': {'term': {'type': 'string', 'description': 'Search term.'}, 'limit': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..50 are rejected.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'language': {'type': 'string', 'description': 'Language code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'integer', 'description': 'Hacker News item id.'}, 'depth': {'type': 'integer', 'maximum': 10, 'minimum': 1, 'description': 'Maximum comment-tree depth to resolve; values outside 1..10 are rejected.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['q'], 'properties': {'q': {'type': 'string', 'description': 'Search query.'}, 'page': {'type': 'integer', 'minimum': 0, 'description': 'Result page.'}, 'tags': {'type': 'string', 'description': 'Algolia HN Search tag filter (e.g. story, comment, ask_hn).'}, 'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..100 are rejected.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'page': {'type': 'integer', 'minimum': 1, 'description': 'Result page.'}, 'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..100 are rejected.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'page': {'type': 'integer', 'minimum': 1, 'description': 'Result page.'}, 'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..100 are rejected.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'page': {'type': 'integer', 'minimum': 1, 'description': 'Result page.'}, 'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..100 are rejected.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'page': {'type': 'integer', 'minimum': 1, 'description': 'Result page.'}, 'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..100 are rejected.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'page': {'type': 'integer', 'minimum': 1, 'description': 'Result page.'}, 'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..100 are rejected.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'page': {'type': 'integer', 'minimum': 1, 'description': 'Result page.'}, 'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..100 are rejected.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['handle'], 'properties': {'handle': {'type': 'string', 'description': 'Hacker News username.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'num': {'type': 'integer', 'maximum': 200, 'minimum': 1, 'description': 'Number of applications; values outside 1..200 are rejected.'}, 'lang': {'type': 'string', 'description': 'Language code.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'category': {'type': 'string', 'description': 'Provider category constant.'}, 'collection': {'type': 'string', 'description': 'Provider collection constant.'}, 'fullDetail': {'type': 'boolean', 'description': 'Request detail enrichment.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'format': 'uuid', 'description': 'path parameter'}, 'limit': {'type': 'integer', 'default': 20, 'maximum': 100, 'minimum': 1, 'description': 'query parameter'}, 'cursor': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['Idempotency-Key', 'body'], 'properties': {'body': {'$ref': '#/$defs/MonitorCreateRequest'}, 'Idempotency-Key': {'type': 'string', 'minLength': 1, 'description': 'header parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'format': 'uuid', 'description': 'path parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'limit': {'type': 'integer', 'default': 20, 'maximum': 100, 'minimum': 1, 'description': 'query parameter'}, 'cursor': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'format': 'uuid', 'description': 'path parameter'}, 'limit': {'type': 'integer', 'default': 20, 'maximum': 100, 'minimum': 1, 'description': 'query parameter'}, 'cursor': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id', 'body'], 'properties': {'id': {'type': 'string', 'format': 'uuid', 'description': 'path parameter'}, 'body': {'$ref': '#/$defs/MonitorUpdateRequest'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'path parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['permalink'], 'properties': {'permalink': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'public application identifier.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'id': {'type': 'string', 'description': 'public application identifier; provide this or appId.'}, 'appId': {'type': 'string', 'description': 'Bundle id; provide this or id.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'pattern': '^https?://', 'description': 'Public PDF URL to read.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'id': {'type': 'string', 'description': 'public application identifier; provide this or appId.'}, 'page': {'type': 'integer', 'maximum': 10, 'minimum': 1, 'description': 'Review page; values outside 1..10 are rejected.'}, 'sort': {'type': 'string', 'description': 'Provider review sort constant.'}, 'appId': {'type': 'string', 'description': 'Bundle id; provide this or id.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['term'], 'properties': {'num': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'Number of results; values outside 1..50 are rejected.'}, 'lang': {'type': 'string', 'description': 'Language code.'}, 'page': {'type': 'integer', 'maximum': 200, 'minimum': 1, 'description': 'Result page.'}, 'term': {'type': 'string', 'description': 'Search term.'}, 'country': {'type': 'string', 'description': 'Two-letter storefront code.'}, 'idsOnly': {'type': 'boolean', 'description': 'Return ids without detail lookups.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['query'], 'properties': {'sort': {'type': 'string', 'description': 'query parameter'}, 'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'query parameter'}, 'query': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'properties': {'id': {'type': 'string', 'description': 'public application identifier; provide this or appId.'}, 'appId': {'type': 'string', 'description': 'Bundle id; provide this or id.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['subreddit'], 'properties': {'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'query parameter'}, 'category': {'type': 'string', 'description': 'query parameter'}, 'subreddit': {'type': 'string', 'description': 'path parameter'}, 'time_filter': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['term'], 'properties': {'term': {'type': 'string', 'description': 'Search term.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['username'], 'properties': {'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'query parameter'}, 'username': {'type': 'string', 'description': 'path parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['username'], 'properties': {'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'query parameter'}, 'category': {'type': 'string', 'description': 'query parameter'}, 'username': {'type': 'string', 'description': 'path parameter'}, 'time_filter': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Target URL to fetch. Rejected if it is malformed, uses a disallowed scheme, or resolves to a private/loopback/link-local/cloud-metadata address.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Target URL to fetch. Rejected if it is malformed, uses a disallowed scheme, or resolves to a private/loopback/link-local/cloud-metadata address.'}, 'max_depth': {'type': 'integer', 'maximum': 3, 'minimum': 1, 'description': 'Maximum link depth; clamped to 1..3.'}, 'max_pages': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'Maximum pages to crawl; clamped to 1..50.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Target URL to fetch. Rejected if it is malformed, uses a disallowed scheme, or resolves to a private/loopback/link-local/cloud-metadata address.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Target URL to fetch. Rejected if it is malformed, uses a disallowed scheme, or resolves to a private/loopback/link-local/cloud-metadata address.'}, 'exclude_selectors': {'type': 'string', 'description': 'Comma-separated CSS selectors to exclude (e.g. "nav,.sidebar,footer").'}, 'include_selectors': {'type': 'string', 'description': 'Comma-separated CSS selectors to include (e.g. "article,.content").'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['text'], 'properties': {'date': {'type': 'string', 'description': 'Provider date filter.'}, 'lang': {'type': 'string', 'description': 'Language code.'}, 'site': {'type': 'string', 'description': 'Restrict results to a site or domain.'}, 'text': {'type': 'string', 'description': 'Search query text.'}, 'limit': {'type': 'integer', 'maximum': 100, 'minimum': 1, 'description': 'Maximum number of results; values outside 1..100 are rejected.'}, 'start': {'type': 'integer', 'minimum': 0, 'description': 'Zero-based result offset.'}, 'region': {'type': 'string', 'description': 'Search region.'}, 'engines': {'type': 'string', 'description': 'Comma-separated search engines.'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'path parameter'}, 'language': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'path parameter'}, 'limit': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'path parameter'}, 'language': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'path parameter'}, 'language': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['term'], 'properties': {'term': {'type': 'string', 'description': 'query parameter'}, 'limit': {'type': 'integer', 'maximum': 50, 'minimum': 1, 'description': 'query parameter'}, 'language': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'path parameter'}, 'language': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Esquema de entrada
{'type': 'object', '$defs': {'Error': {'type': 'object', 'properties': {'error': {'type': 'object', 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}}}}, 'Monitor': {'type': 'object', 'required': ['id', 'url', 'detector', 'frequency', 'paused'], 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'url': {'type': 'string', 'format': 'uri'}, 'paused': {'type': 'boolean'}, 'webhook': {'allOf': [{'$ref': '#/$defs/MonitorWebhook'}], 'description': 'Secret is write-only and never returned'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}, 'created_at': {'type': 'string', 'format': 'date-time'}, 'sync_state': {'enum': ['none', 'pending', 'running', 'retryable', 'failed', 'synced'], 'type': 'string', 'description': 'External changedetection watch reconciliation state. PATCH persists frequency/paused immediately (the database is the scheduling authority); the worker applies the change to the engine\'s watch asynchronously and exposes convergence here. "none" means no external watch exists yet; "pending"/"retryable" are awaiting or retrying the application; "running" is being applied; "failed" exhausted the bounded retry budget and needs a new PATCH to re-arm; "synced" means the engine watch matches the record.'}, 'updated_at': {'type': 'string', 'format': 'date-time'}, 'next_run_at': {'type': 'string', 'format': 'date-time', 'description': "Canonical next scheduled run in UTC. Reported only while the monitor is schedulable: a monitor whose private watch is still being initialized cannot be claimed (ClaimDue requires initialization_state='ready'), so the field is omitted until initialization completes instead of implying an imminent run. Paused-but-initialized monitors still report their stored schedule; it is suspended until resume."}, 'sync_error_reason': {'type': 'string', 'description': 'Most recent changedetection engine watch reconciliation failure reason'}, 'baseline_completed': {'type': 'boolean'}, 'sync_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable engine watch reconciliation'}, 'initialization_state': {'enum': ['pending', 'initializing', 'retryable', 'ready', 'failed'], 'type': 'string', 'description': 'Asynchronous changedetection watch initialization state'}, 'initialization_error_reason': {'type': 'string', 'description': 'Most recent changedetection initialization failure reason'}, 'initialization_next_retry_at': {'type': 'string', 'format': 'date-time', 'description': 'Next worker retry time for retryable initialization'}}}, 'MonitorRun': {'type': 'object', 'properties': {'id': {'type': 'string', 'format': 'uuid'}, 'status': {'type': 'string'}, 'changed': {'type': 'boolean'}, 'credits': {'type': 'integer'}, 'baseline': {'type': 'boolean'}, 'timestamp': {'type': 'string', 'format': 'date-time'}}}, 'Pagination': {'type': 'object', 'properties': {'page': {'type': 'integer'}, 'limit': {'type': 'integer'}, 'has_more': {'type': 'boolean'}, 'next_cursor': {'type': 'string'}}, 'additionalProperties': True}, 'ErrorDetail': {'type': 'object', 'required': ['code', 'message', 'request_id'], 'properties': {'code': {'type': 'string'}, 'message': {'type': 'string'}, 'request_id': {'type': 'string'}}, 'additionalProperties': False}, 'MonitorPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/Monitor'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'ResponseMeta': {'type': 'object', 'required': ['request_id'], 'properties': {'stale': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}, 'availability': {'type': 'string'}, 'generated_at': {'type': 'string', 'format': 'date-time'}, 'missing_fields': {'type': 'array', 'items': {'type': 'string'}}, 'contract_version': {'type': 'string'}}, 'additionalProperties': True}, 'BrandLogoData': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'The snapshot target URL the logos were extracted from.'}, 'brand': {'type': 'object', 'properties': {'name': {'type': 'string', 'description': 'Public site/brand name detected on the page, when available.'}, 'logos': {'type': 'array', 'items': {'type': 'object', 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'kind': {'type': 'string', 'description': 'Asset slot and format (e.g. logo.svg, favicon.ico).'}}}, 'description': 'Candidate logo variants detected on the page.'}, 'logo_url': {'type': 'string', 'format': 'uri', 'description': 'Primary brand logo URL, when available.'}, 'og_image': {'type': 'string', 'format': 'uri'}, 'favicon_url': {'type': 'string', 'format': 'uri'}}}}, 'additionalProperties': False}, 'ErrorResponse': {'type': 'object', 'required': ['error'], 'properties': {'error': {'$ref': '#/$defs/ErrorDetail'}}, 'additionalProperties': False}, 'BrandFontsData': {'type': 'object', 'required': ['domain', 'url', 'count'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'count': {'type': 'integer', 'minimum': 0}, 'fonts': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, 'domain': {'type': 'string'}, 'words_count': {'type': 'integer', 'minimum': 0, 'description': 'Word count reported by the rendered page metadata, when available.'}}, 'additionalProperties': False}, 'MonitorWebhook': {'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'secret': {'type': 'string', 'writeOnly': True, 'description': 'HMAC secret used to sign deliveries. Write-only: it is never returned, logged, or rendered into the payload.'}}, 'description': "Signed webhook notification endpoint (issue #485). The URL must be a public HTTPS target; localhost/private/link-local/metadata/CGNAT destinations and non-HTTPS schemes are rejected unless the service is explicitly configured otherwise for a non-production harness. The secret is AES-256-GCM encrypted at rest. When a monitor change is detected the service POSTs the MonitorChangeEvent payload to this URL with headers X-ReplyNodes-Event-Id (<event_id>), X-ReplyNodes-Timestamp (Unix seconds of this attempt) and X-ReplyNodes-Signature (sha256=<hex> HMAC-SHA256 over timestamp + '.' + raw request body). Receivers should verify against the exact raw body bytes and enforce a 5-minute timestamp tolerance; retries re-sign with a fresh timestamp and the same event_id. Destination identity is URL-based and rotation is replace-on-update with no overlap: a PATCH changing only webhook.secret keeps the same destination identity and in-flight deliveries are signed with the current secret, while a PATCH changing the URL finalizes rows for the old URL as destination_replaced. When HTTPS is required, every redirect is revalidated and a redirect to a cleartext http target is rejected; the secret is write-only, AES-256-GCM encrypted at rest, and never returned or logged."}, 'RedditResponse': {'type': 'object', 'additionalProperties': True}, 'MonitorEnvelope': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/Monitor'}, 'meta': {'type': 'object', 'required': ['request_id'], 'properties': {'request_id': {'type': 'string'}, 'initialization': {'type': 'string', 'description': 'Present on the asynchronous 202 create response while the private watch is being initialized: "pending". The monitor is not schedulable until initialization completes, so next_run_at is omitted from the data block until then.'}}}}}, 'MonitorPageMeta': {'type': 'object', 'required': ['request_id', 'has_more'], 'properties': {'has_more': {'type': 'boolean'}, 'request_id': {'type': 'string'}, 'next_cursor': {'type': 'string'}}}, 'PaymentRequired': {'type': 'object', 'required': ['x402Version', 'error', 'resource', 'accepts', 'extensions'], 'properties': {'error': {'type': 'string'}, 'accepts': {'type': 'array', 'items': {'$ref': '#/$defs/PaymentRequirements'}, 'minItems': 1}, 'resource': {'type': 'object', 'required': ['url', 'mimeType'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'mimeType': {'type': 'string'}}, 'additionalProperties': False}, 'extensions': {'oneOf': [{'$ref': '#/$defs/PaymentRequiredAccountlessExtensions'}, {'$ref': '#/$defs/PaymentRequiredPrepaidExtensions'}]}, 'x402Version': {'enum': [2], 'type': 'integer'}}, 'additionalProperties': False}, 'SuccessResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandLogoResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandLogoData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'BrandFontsResponse': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'$ref': '#/$defs/BrandFontsData'}, 'meta': {'$ref': '#/$defs/ResponseMeta'}}, 'additionalProperties': False}, 'MonitorChangeEvent': {'type': 'object', 'required': ['event_version', 'event_id', 'event_type', 'monitor_id', 'run_id', 'detector', 'url', 'change_type', 'importance', 'summary', 'detected_at'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Monitored public URL.'}, 'run_id': {'type': 'string', 'format': 'uuid'}, 'summary': {'type': 'string', 'description': 'Bounded, page-content-free human summary.'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'event_id': {'type': 'string', 'format': 'uuid', 'description': 'Stable canonical event identity (X-ReplyNodes-Event-Id); identical for every delivery attempt.'}, 'evidence': {'type': 'object', 'properties': {'bytes': {'type': 'integer', 'format': 'int64'}, 'commit': {'type': 'string'}, 'repository': {'type': 'string'}, 'manifest_path': {'type': 'string'}, 'manifest_sha256': {'type': 'string'}}, 'description': 'Durable evidence/diff reference for this run (repository identifiers and content hashes, never page content).'}, 'event_type': {'enum': ['monitor.change.detected'], 'type': 'string'}, 'importance': {'enum': ['low', 'medium', 'high'], 'type': 'string'}, 'monitor_id': {'type': 'string', 'format': 'uuid'}, 'change_type': {'type': 'string', 'description': 'Bounded detector change kind resolved by the detector/Jev judgement.'}, 'detected_at': {'type': 'string', 'format': 'date-time'}, 'event_version': {'type': 'integer', 'description': 'Canonical envelope version. Receivers must branch on it.'}, 'jev_confidence': {'type': 'number', 'description': 'Raw Jev provider probability for a valid semantic judgement; present only when applicable and not calibrated confidence.'}}, 'description': 'Versioned canonical change event delivered to a configured signed webhook. The payload is exact raw bytes persisted at event creation; Delivery re-signs those bytes on every attempt. organization_id and all secrets are internal and never appear in this document.'}, 'MonitorHistoryPage': {'type': 'object', 'required': ['data', 'meta'], 'properties': {'data': {'type': 'array', 'items': {'$ref': '#/$defs/MonitorRun'}}, 'meta': {'$ref': '#/$defs/MonitorPageMeta'}}}, 'CreditTopupRequired': {'type': 'object', 'required': ['code', 'balance_micro', 'price_micro', 'topup_url'], 'properties': {'code': {'enum': ['topup_required'], 'type': 'string'}, 'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}, 'price_micro': {'type': 'integer'}, 'balance_micro': {'type': 'integer'}}, 'additionalProperties': False}, 'PaymentRequirements': {'type': 'object', 'required': ['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds'], 'properties': {'asset': {'type': 'string'}, 'extra': {'type': 'object', 'additionalProperties': True}, 'payTo': {'type': 'string'}, 'amount': {'type': 'string'}, 'scheme': {'type': 'string'}, 'network': {'type': 'string'}, 'maxTimeoutSeconds': {'type': 'integer'}}, 'additionalProperties': False}, 'MonitorCreateRequest': {'type': 'object', 'required': ['url', 'detector', 'frequency'], 'properties': {'url': {'type': 'string', 'format': 'uri'}, 'webhook': {'$ref': '#/$defs/MonitorWebhook'}, 'detector': {'enum': ['price', 'saas_pricing', 'product_change'], 'type': 'string'}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}}, 'MonitorUpdateRequest': {'type': 'object', 'properties': {'paused': {'type': 'boolean'}, 'webhook': {'anyOf': [{'$ref': '#/$defs/MonitorWebhook'}, {'type': 'null'}]}, 'frequency': {'enum': ['15m', '1h', '6h', '12h', '24h'], 'type': 'string'}}, 'description': 'Update frequency, pause state, or webhook configuration. The database update is authoritative and committed immediately; the changedetection engine watch is reconciled asynchronously by the monitor worker and the returned monitor reports convergence through sync_state/sync_next_retry_at/sync_error_reason instead of claiming engine success. Reconciliation failures retry with a bounded backoff budget and surface as sync_state="failed" (a new PATCH re-arms it). An active monitor\'s schedule reflects the new preset promptly: changing frequency recomputes next_run_at from the change time using the new preset, and resuming a paused monitor schedules the next run one interval after the resume (never an immediate run), so the engine and the canonical schedule cannot drift onto different cadences. Immutable fields (url, detector) are rejected.'}, 'PaymentRequiredResponse': {'oneOf': [{'$ref': '#/$defs/PaymentRequired'}, {'$ref': '#/$defs/CreditTopupRequired'}]}, 'PaymentRequiredPrepaidExtensions': {'type': 'object', 'required': ['topup'], 'properties': {'topup': {'type': 'object', 'required': ['topup_url'], 'properties': {'topup_url': {'enum': ['/v1/billing/topup/intents'], 'type': 'string'}}, 'additionalProperties': False}}, 'additionalProperties': False}, 'PaymentRequiredAccountlessExtensions': {'type': 'object', 'required': ['x-replynodes-accountless'], 'properties': {'x-replynodes-accountless': {'type': 'object', 'required': ['payment_mode', 'bearer_supported', 'prepaid_supported'], 'properties': {'payment_mode': {'enum': ['x402_only'], 'type': 'string'}, 'bearer_supported': {'enum': [False], 'type': 'boolean'}, 'prepaid_supported': {'enum': [False], 'type': 'boolean'}}, 'additionalProperties': False}}, 'additionalProperties': False}}, 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'path parameter'}, 'language': {'type': 'string', 'description': 'query parameter'}}, 'additionalProperties': False}
Cambios recientes en herramientas
Servidores MCP similares
LiveDataLink
Exposes public-data tools covering economics, labor, banking, transport, books, legal opinions, carriers, environmental data, and…
Stratalize Healthcare
Retired Stratalize server exposing benchmark, market intelligence, compliance, financial, healthcare, legal, real estate, and eco…
Stratalize Governance
Retired Stratalize server exposing benchmark, market intelligence, compliance, financial, healthcare, legal, real estate, and eco…
Stratalize Oracle
Retired Stratalize server exposing benchmark, market intelligence, compliance, financial, healthcare, legal, real estate, and eco…
Stratalize Real Estate
Retired Stratalize server exposing benchmark, market intelligence, compliance, financial, healthcare, legal, real estate, and eco…
Stratalize Crypto & DeFi
Retired Stratalize server exposing benchmark, market intelligence, compliance, financial, healthcare, legal, real estate, and eco…
Valuein — SEC EDGAR Fundamentals & Smart-Money Data
Provides point-in-time SEC EDGAR fundamentals, filings, ownership signals, financial analysis, valuation models, research reports…
equibles
Provides equity and market research tools covering SEC filings, company financials, portfolios, prices, options, macroeconomic da…