{"openapi":"3.1.0","info":{"title":"RoundWatch API","version":"1.0.0","summary":"Durable Algorand USDC payment monitoring for autonomous workflows","description":"RoundWatch creates one durable watch for an exact future top-level direct Algorand USDC payment when no transaction ID exists yet. Inner transactions, clawback transfers, and asset close-out transfers are outside the current matching contract. The create operation is paid through x402; status retrieval is free.","license":{"name":"MIT","identifier":"MIT"},"contact":{"url":"https://roundwatch.observer"}},"servers":[{"url":"https://roundwatch-api.onrender.com","description":"RoundWatch mainnet API"}],"externalDocs":{"description":"RoundWatch quickstart","url":"https://roundwatch.observer/start"},"tags":[{"name":"RoundWatch","description":"Create and retrieve durable watches for exact future Algorand USDC payments."},{"name":"Service","description":"Free service metadata and health endpoints."}],"paths":{"/health":{"get":{"tags":["Service"],"operationId":"getHealth","summary":"Check RoundWatch process liveness","description":"Liveness only. A 200 response does not claim that durable storage and background workers are ready to accept paid obligations.","responses":{"200":{"description":"The HTTP process is alive.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/ready":{"get":{"tags":["Service"],"operationId":"getReadiness","summary":"Check durable-service readiness","description":"Readiness checks cached durable-write capability plus production worker progress/error freshness. A non-ready service refuses new paid watch obligations before x402 verification. Use this endpoint before directing paid traffic.","responses":{"200":{"description":"RoundWatch is ready to accept paid obligations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadinessResponse"}}}},"503":{"description":"The process is alive but the durable service is not ready.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadinessResponse"}}}}}}},"/v1/watch":{"post":{"tags":["RoundWatch"],"operationId":"createWatch","summary":"Create a durable watch for one exact future USDC payment","description":"Submit the expected sender, receiver, atomic amount, and optional exact note for one top-level direct USDC asset transfer. Inner transactions, clawback transfers, and asset close-out transfers do not count as matches. The service contract includes a 500-turn work budget. Eligibility lasts 1800000 ms from durable watch preparation before x402 settlement completes, so settlement time consumes the window. The watched payment must confirm in a round strictly greater than activationRound; a same-round payment is ineligible. Its Algorand block round-time must be strictly earlier than expiresAt; a payment exactly at the deadline is ineligible. For unpaid discovery, RoundWatch returns HTTP 402 with PAYMENT-REQUIRED and Bazaar request metadata before semantic body validation so body-dependent clients can learn the required request shape. On the paid retry, RoundWatch parses and validates the watch specification before any facilitator verification or settlement; malformed signed input returns HTTP 400 and cannot spend. After a valid PAYMENT-SIGNATURE is settled and durable activation is confirmed, the same request returns a watch ID. The watched asset is selected by the server and is not supplied by the caller.","x-x402":{"version":2,"scheme":"exact","network":"algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=","asset":"31566704","payTo":"EQPLN32HPLPGBCNPOZUL6BL34CTNQGT3VAAMNAJWSIZGQ5CUNXOHB634XY","servicePriceUsd":"0.02","servicePriceAtomicAmount":"20000","challengeTag":"x402-global-challenge","requestHeader":"PAYMENT-SIGNATURE","challengeHeader":"PAYMENT-REQUIRED","settlementHeader":"PAYMENT-RESPONSE","watchEligibility":{"ttlMs":1800000,"startsAt":"durable_watch_preparation_before_x402_settlement","settlementTimeConsumesEligibilityWindow":true,"roundBoundary":{"field":"confirmed-round","operator":">","reference":"activationRound","sameActivationRoundEligible":false,"example":"If activationRound is 100, confirmed round 100 is ineligible and round 101 is the first eligible round."},"timeBoundary":{"field":"round-time","operator":"<","reference":"expiresAt","exactDeadlineEligible":false,"timestampPrecision":"seconds","example":"A transaction whose block timestamp equals expiresAt is ineligible; the block timestamp must be strictly earlier."}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWatchRequest"},"example":{"idempotencyKey":"invoice-2026-09-22-001","expectedSender":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ","expectedReceiver":"AEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEA5RCDXMI","atomicAmount":"1000000","invoiceNote":"roundwatch:invoice-2026-09-22-001"}}}},"responses":{"200":{"description":"x402 settlement succeeded and the durable watch was activated.","headers":{"PAYMENT-RESPONSE":{"description":"x402 settlement response exposed to browser clients.","schema":{"type":"string"}},"X-RoundWatch-Id":{"description":"Durable RoundWatch watch ID.","schema":{"type":"string","format":"uuid"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWatchResponse"}}}},"400":{"description":"Invalid signed watch request rejected before facilitator verification/settlement, or invalid PAYMENT-SIGNATURE header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment required and discovery metadata available. This unpaid response may be returned before semantic validation of the submitted body; use the declared OpenAPI/Bazaar request shape, then retry with PAYMENT-SIGNATURE. Signed retries are validated before facilitator verification or settlement.","headers":{"PAYMENT-REQUIRED":{"required":true,"description":"Base64-encoded canonical x402 v2 payment challenge.","schema":{"type":"string"}}}},"409":{"description":"The idempotency key already belongs to an existing watch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"RoundWatch or payment-verification admission capacity is temporarily exhausted.","headers":{"Retry-After":{"description":"Retry delay in seconds when payment verification is rate-limited.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Settlement or durable activation could not be confirmed safely.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"RoundWatch is alive but durable storage or background workers are not ready to accept a paid obligation. Retry after /ready returns 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/watch/recover":{"post":{"tags":["RoundWatch"],"operationId":"recoverWatch","summary":"Recover an existing watch after losing the paid create response","description":"Free exact recovery lookup. Use this after a paid create may have succeeded but the caller lost the returned watchId. Submit the exact original watch specification plus the public Algorand service-payer address that signed the x402 service payment. This endpoint never verifies or settles a payment. It returns an exact watch once service settlement was confirmed, including durable terminal states such as expired or post-activation indeterminate. Unsettled or settlement-ambiguous obligations remain non-recoverable. Do not repay merely because the original response was lost.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoverWatchRequest"}}}},"responses":{"200":{"description":"The exact previously paid watch was found and is recoverable.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["watch"],"properties":{"watch":{"$ref":"#/components/schemas/Watch"}}}}}},"400":{"description":"Invalid recovery request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No exact recoverable watch matches the supplied specification and service payer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A matching obligation exists but is not currently recoverable. Inspect state and recovery disposition; retry only when recovery.retryable is true. The response intentionally does not expose the watch ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryConflictResponse"}}}},"429":{"description":"Free recovery lookup capacity is temporarily exhausted.","headers":{"Retry-After":{"description":"Retry delay in seconds.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/watch/{id}":{"get":{"tags":["RoundWatch"],"operationId":"getWatch","summary":"Retrieve durable watch state and evidence","description":"Free status lookup. The caller may exit after successful creation and retrieve the watch later using its durable ID.","parameters":[{"name":"id","in":"path","required":true,"description":"RoundWatch watch ID returned by createWatch.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Current durable watch state.","content":{"application/json":{"schema":{"type":"object","required":["watch"],"properties":{"watch":{"$ref":"#/components/schemas/Watch"}}}}}},"404":{"description":"No watch exists with this ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"HealthResponse":{"type":"object","additionalProperties":false,"required":["status","purpose","network"],"properties":{"status":{"type":"string","const":"ok"},"purpose":{"type":"string","const":"liveness"},"network":{"type":"string","enum":["mainnet","testnet"]}}},"ReadinessResponse":{"type":"object","additionalProperties":false,"required":["status","network","checks"],"properties":{"status":{"type":"string","enum":["ready","not_ready"]},"network":{"type":"string","enum":["mainnet","testnet"]},"checks":{"type":"object","additionalProperties":{"type":"boolean"}}}},"CreateWatchRequest":{"type":"object","additionalProperties":true,"required":["idempotencyKey","expectedSender","expectedReceiver","atomicAmount"],"properties":{"idempotencyKey":{"type":"string","minLength":8,"maxLength":128,"description":"Stable caller-supplied key used to prevent duplicate durable watches for the same payment intent."},"expectedSender":{"type":"string","minLength":58,"maxLength":58,"description":"Checksum-valid Algorand address expected to send the future USDC payment."},"expectedReceiver":{"type":"string","minLength":58,"maxLength":58,"description":"Checksum-valid Algorand address expected to receive the future USDC payment."},"atomicAmount":{"type":"string","pattern":"^[1-9]\\d*$","description":"Exact watched USDC amount in atomic units. Algorand USDC uses 6 decimals, so 1000000 means 1 USDC."},"invoiceNote":{"type":"string","minLength":1,"maxLength":128,"description":"Optional exact UTF-8 Algorand transaction note. The runtime enforces a maximum of 128 UTF-8 bytes."}}},"RecoverWatchRequest":{"allOf":[{"$ref":"#/components/schemas/CreateWatchRequest"},{"type":"object","required":["servicePayer"],"properties":{"servicePayer":{"type":"string","minLength":58,"maxLength":58,"description":"Checksum-valid public Algorand address that signed the x402 RoundWatch service payment for the original create attempt."}}}]},"CreateWatchResponse":{"type":"object","additionalProperties":false,"required":["watchId","workUnitBudget","eligibilityTtlMs","eligibility","expiresAt","message"],"properties":{"watchId":{"type":"string","format":"uuid"},"workUnitBudget":{"type":"integer","minimum":1,"example":500},"eligibilityTtlMs":{"type":"integer","minimum":1,"example":1800000,"description":"Creation-based eligibility window in milliseconds, disclosed before purchase."},"eligibility":{"type":"object","additionalProperties":true,"example":{"ttlMs":1800000,"startsAt":"durable_watch_preparation_before_x402_settlement","settlementTimeConsumesEligibilityWindow":true,"roundBoundary":{"field":"confirmed-round","operator":">","reference":"activationRound","sameActivationRoundEligible":false,"example":"If activationRound is 100, confirmed round 100 is ineligible and round 101 is the first eligible round."},"timeBoundary":{"field":"round-time","operator":"<","reference":"expiresAt","exactDeadlineEligible":false,"timestampPrecision":"seconds","example":"A transaction whose block timestamp equals expiresAt is ineligible; the block timestamp must be strictly earlier."}},"description":"Strict watch eligibility contract. confirmed-round must be > activationRound and round-time must be < expiresAt."},"expiresAt":{"type":"string","format":"date-time","description":"Exact eligibility deadline persisted when the durable watch is prepared."},"message":{"type":"string"}}},"Watch":{"type":"object","additionalProperties":false,"required":["id","state","expectedSender","expectedReceiver","assetId","atomicAmount","createdAt","workUnitsUsed"],"properties":{"id":{"type":"string","format":"uuid"},"state":{"type":"string","enum":["settlement_pending","active","matched","settlement_unknown","expired","indeterminate"]},"expectedSender":{"type":"string","minLength":58,"maxLength":58},"expectedReceiver":{"type":"string","minLength":58,"maxLength":58},"assetId":{"type":"integer","example":31566704},"atomicAmount":{"type":"string"},"invoiceNote":{"type":"string"},"expectedServiceTransaction":{"type":"string"},"expectedServiceNetwork":{"type":"string"},"expectedServicePayer":{"type":"string"},"serviceTransaction":{"type":"string"},"serviceNetwork":{"type":"string"},"servicePayer":{"type":"string"},"activationRound":{"type":"integer","minimum":0},"activatedAt":{"type":"string","format":"date-time"},"scanAfterRound":{"type":"integer","minimum":0},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"matchedTransaction":{"type":"string"},"matchedRound":{"type":"integer","minimum":0},"workUnitBudget":{"type":"integer","minimum":1},"workUnitsUsed":{"type":"integer","minimum":0},"settlementReconciliationTerminal":{"type":"boolean","description":"True when settlement reconciliation has reached a final fail-closed outcome and will not retry."},"terminalReason":{"type":"string","enum":["work_budget_exhausted"]}}},"RecoveryConflictResponse":{"type":"object","additionalProperties":false,"required":["error","state","recovery"],"properties":{"error":{"type":"string"},"state":{"type":"string","enum":["settlement_pending","settlement_unknown","indeterminate"]},"recovery":{"type":"object","additionalProperties":false,"required":["retryable","terminal","reason","nextAction"],"properties":{"retryable":{"type":"boolean"},"terminal":{"type":"boolean"},"reason":{"type":"string","enum":["settlement_reconciliation_pending","settlement_reconciliation_terminal","watch_terminal_nonrecoverable"]},"nextAction":{"type":"string","enum":["retry_recovery_later","retain_checkpoint_and_investigate"]}}}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"},"watchId":{"type":"string","format":"uuid"},"watch":{"$ref":"#/components/schemas/Watch"}}}}}}