Documentation Index
Fetch the complete documentation index at: https://docs.algovoi.co.uk/llms.txt
Use this file to discover all available pages before exploring further.
The AlgoVoi recurring payment engine is the subsystem responsible for scheduled, pre-authorised payments across all seven supported chains. It supports two distinct recurring payment models — MPP subscription protocol (Tier 1) and standing authorities (Tier 2) — and is accessible at recurr.algovoi.co.uk.
Both models share a common pull executor, a common mandate data model, and common integration with the five audit chains. All pull transactions are settled on-chain with full receipt-format emission, making every recurring charge auditable, cancellable, and refundable under the same PSD2-aligned framework as one-off payments.
Architecture Overview
| Component | Role |
|---|
| Recurring engine | Hosts the MPP subscription endpoint, the standing authority API, and the pull executor scheduler |
| Control plane | Mandate creation, activation, state management, safeguard enforcement |
| Facilitator (active-passive cluster) | Chain-specific RPC submission for pull transactions |
| Audit chains | payment_ledger, compliance_events, and negotiation_trace_events receive a row for every pull |
| Object Lock storage | All audit rows shipped off-VM within approximately 5 minutes of write |
The pull executor runs as a scheduled in-process job. It evaluates next_due_at across all active mandates and standing authorities on a sub-minute cadence and submits pull transactions when due.
Mandate Data Model
Every recurring arrangement — whether a Tier 1 MPP subscription or a Tier 2 standing authority — is represented as a mandate record. The mandate record is the single source of truth for scheduling, cap enforcement, and state management.
Mandate Fields
| Field | Type | Values / Description |
|---|
| id | uuid | Primary key |
| tenant_id | uuid | The merchant tenant that owns this mandate |
| resource_id | string | The payable resource this mandate covers; used in MPP subscription URLs |
| payer_address | string | The on-chain wallet that authorised the mandate |
| chain | enum | algorand, voi, hedera, stellar, base, solana, or tempo |
| asset_id | string | CAIP-19 asset identifier (e.g. eip155:8453/erc20:0x… for Base USDC) |
| cap_microunits | bigint | Maximum amount per pull execution, in the smallest on-chain denomination |
| period_unit | enum | day, week, or month |
| period_count | integer | Number of period_unit intervals between pulls (e.g. period_count=30, period_unit=day means every 30 days) |
| mandate_type | enum | mpp_subscription (Tier 1), standing_authority (Tier 2) |
| status | enum | pending, active, paused, revoked, expired, cancelled |
| activated_at | timestamp | ISO 8601 UTC timestamp when on-chain signature was confirmed by the facilitator |
| next_due_at | timestamp | ISO 8601 UTC timestamp of the next scheduled pull |
| last_pull_at | timestamp | ISO 8601 UTC timestamp of the most recent successful pull |
| last_pull_tx_id | string | On-chain transaction ID of the most recent pull |
| cancel_at | timestamp | Scheduled cancellation date; null if no scheduled cancellation |
| cancel_reason | enum | user_requested, merchant_requested, compliance_terminated, expired, or null |
| per_cycle_microunits | bigint | Tier 2 only. Maximum amount per pull (may be less than cap_microunits if cap is a lifetime cap) |
| lifetime_cap_microunits | bigint | Tier 2 only. Lifetime cap across all pulls under this authority. Null if uncapped. |
| total_pulled_microunits | bigint | Running total of all amounts successfully pulled under this mandate |
| created_at | timestamp | ISO 8601 UTC creation timestamp |
| updated_at | timestamp | ISO 8601 UTC last-update timestamp |
Sealed Audit Fields
The following fields are sealed at insert into the payment_ledger audit chain on every pull and cannot be altered retrospectively:
tenant_id, resource_id, tx_id (per pull), chain, asset_id, amount_microunits (per pull), payer_address, verified_at, status, protocol (recurr)
The following limits are enforced at the control plane level for all mandate types. They are not configurable at tenant level.
| Safeguard | Limit | Enforcement |
|---|
| Per-mandate cap | £100 maximum cap_microunits (in GBP equivalent at activation time) | Enforced at mandate creation; mandate rejected if cap exceeds limit |
| Per-account total cap | £300 maximum sum of active mandate caps per payer account | Enforced at mandate creation; mandate rejected if it would cause the account total to exceed the limit |
| Maximum concurrent mandates | 3 active mandates per payer account | Enforced at mandate creation; mandate rejected if the payer already has 3 active mandates |
These limits are aligned with EMR 2011 Regulations 20–21 safeguarding requirements for payment institutions holding funds on behalf of customers.
PSD2-aligned behaviour: when a mandate is cancelled with cancel_reason = user_requested, a cancellation receipt is emitted automatically per draft-hopley-x402-cancellation-receipt. This satisfies PSD2 Article 64 requirements for the customer’s right to revoke a payment authorisation.
Mandate Lifecycle State Machine
Mandates move through states according to the following transitions. No transition is possible outside this defined state machine.
States
| State | Description |
|---|
| pending | Mandate record created; awaiting on-chain wallet signature confirmation |
| active | On-chain signature confirmed by facilitator; pulls scheduled |
| paused | Pulls suspended; no on-chain action required; resumes on operator or tenant action |
| revoked | Payer revoked the on-chain authorisation; facilitator detected revocation; no further pulls |
| expired | cancel_at timestamp reached; no further pulls |
| cancelled | Operator or tenant cancelled with an explicit cancel_reason; cancellation receipt emitted |
Transitions
| From | To | Trigger | Side Effects |
|---|
| pending | active | Facilitator confirms on-chain wallet signature | activated_at set; next_due_at calculated; compliance_events row written |
| active | paused | Tenant or operator pauses | next_due_at preserved but not acted on; compliance_events row written |
| paused | active | Tenant or operator resumes | next_due_at recalculated from resume time or preserved (configurable); compliance_events row written |
| active | revoked | Facilitator detects on-chain revocation | No further pulls; mandate.revoked webhook emitted; compliance_events row written |
| active | expired | cancel_at reached | No further pulls; mandate.expired webhook emitted; compliance_events row written |
| paused | expired | cancel_at reached while paused | As above |
| active | cancelled | Tenant or operator cancels | cancel_reason set; cancellation receipt emitted; mandate.cancelled webhook emitted |
| paused | cancelled | As above | As above |
| pending | cancelled | Merchant cancels before activation | No cancellation receipt (no payment was authorised) |
Tier 1: MPP Subscription Protocol
The Merchant Payment Protocol (MPP) subscription model is the standard recurring payment flow for merchants integrating with payer applications, AI agents, and wallets that support the paymentauth.org/mpp method string.
Resource Registration
A subscription resource is registered by the merchant at a deterministic URL:
POST /mpp/sub//
Where tenant_short_id is the merchant’s short identifier (visible in the dashboard) and resource_id is the merchant’s internal identifier for the subscription offering (e.g. a plan ID or product slug).
Subscription Intent Flow
- An agent or payer issues a GET or POST to the subscription resource URL.
- The Recurr service responds with HTTP 402 Payment Required and a subscription intent body.
- The payer application reads the intent body and presents the subscription offer to the end user.
- The payer signs a standing authority credential on-chain for the requested resource.
- The facilitator confirms the on-chain signature to the control plane.
- The mandate transitions from pending to active.
- The pull executor charges the payer at each due date by constructing and submitting the on-chain pull transaction through the facilitator.
Subscription Intent Body Fields
| Field | Type | Description |
|---|
| type | string | Fixed value: mpp_subscription |
| resourceId | string | The resource_id this subscription covers |
| amount | string | Display amount (e.g. “10.00 USDC”) |
| amount_microunits | bigint | Amount in smallest on-chain denomination |
| asset | string | Asset ticker |
| asset_id | string | CAIP-19 asset identifier |
| network | string | Target chain in paymentauth.org network string format |
| period_unit | enum | day, week, or month |
| period_count | integer | Number of period_unit intervals between pulls |
| method | string | Fixed value: paymentauth.org/mpp |
| merchant_name | string | Display name of the merchant |
| description | string | Human-readable description of the subscription |
| expires | timestamp | ISO 8601 UTC expiry of this intent offer |
| signing_endpoint | string | URL to which the payer submits the on-chain signature credential |
MPP Subscription Status Fields
| Field | Type | Description |
|---|
| subscription_id | uuid | The mandate id for this subscription |
| resource_id | string | The subscribed resource |
| status | enum | active, paused, revoked, or expired |
| activated_at | timestamp | When subscription activated |
| first_pull_tx_id | string | On-chain tx_id of the first pull |
| first_pull_finalized_at | timestamp | Timestamp of first pull confirmation |
| next_due_at | timestamp | Next scheduled pull |
| last_pull_at | timestamp | Most recent successful pull |
| last_pull_tx_id | string | On-chain tx_id of most recent pull |
| total_pulled_microunits | bigint | Running total pulled under this subscription |
MPP Webhook Events
| Event | Trigger |
|---|
| mpp_subscription.activated | On-chain signature confirmed; subscription moves to active |
| mpp_subscription.charged | Pull transaction confirmed on-chain |
| mpp_subscription.charge_failed | Pull attempt failed after full retry schedule |
| mpp_subscription.revoked | Payer revoked on-chain authorisation |
| mpp_subscription.expired | cancel_at reached |
Tier 2: Standing Authorities
Tier 2 standing authorities are a higher-autonomy variant for AI-agent payment scenarios. A customer authorises a spending cap once. The agent or automated system then pulls payments on schedule against that cap without requesting re-authorisation for each individual pull.
Additional Fields (Tier 2 Only)
| Field | Type | Description |
|---|
| per_cycle_microunits | bigint | Maximum amount per individual pull. May be less than cap_microunits. |
| lifetime_cap_microunits | bigint | Lifetime total cap. Null if only per-cycle capping applies. |
| pull_mode | enum | scheduled (automatic on next_due_at) or on_demand (only fires on explicit API call) |
| last_manual_pull_at | timestamp | Timestamp of last pull triggered via the manual pull endpoint |
Manual Pull Endpoint
POST /v1/recurring//pull
Triggers an immediate on-demand pull against a standing authority with pull_mode = on_demand. The pull is subject to per_cycle_microunits and the remaining lifetime cap. The request accepts an optional amount_microunits override (must be less than or equal to per_cycle_microunits).
Response fields:
| Field | Type | Description |
|---|
| pull_id | uuid | Identifier for this specific pull attempt |
| tx_id | string | On-chain transaction ID (present once submitted; may be pending_confirmation) |
| amount_microunits | bigint | Amount pulled |
| status | enum | submitted, confirmed, failed |
| submitted_at | timestamp | When the pull transaction was submitted to the chain |
| confirmed_at | timestamp | When the pull transaction was confirmed (null if pending) |
Tier 2 Chain Support
Tier 2 standing authorities are available on chains where the on-chain signature mechanism supports the required pre-authorisation credential format:
| Chain | Tier 2 Authority Support |
|---|
| Algorand | Supported |
| VOI | Supported |
| Base | Supported |
| Solana | Supported |
| Hedera | Not currently supported |
| Stellar | Not currently supported |
| Tempo | Not currently supported |
Pull Executor
The pull executor is the in-process scheduler that drives all recurring charges. It runs on the recurring payment engine.
Scheduling
The executor runs on a sub-minute cadence. On each tick it queries for all active mandates (both Tier 1 and Tier 2 scheduled) where next_due_at is in the past and status is active. Each due mandate is processed in order of next_due_at ascending.
Per-Pull Execution Steps
For each due mandate, the executor performs the following steps in order:
-
Validate mandate state: confirm status is still active (it may have been revoked between ticks).
-
Check safeguards: confirm that the pull amount does not exceed cap_microunits. For Tier 2 authorities, also confirm that the pull would not exceed lifetime_cap_microunits.
-
Construct pull transaction: build the chain-appropriate pull transaction. The transaction construction is delegated to the chain-specific facilitator module. The constructed transaction includes the on-chain reference to the standing authority credential.
-
Submit to facilitator: POST the raw transaction to the facilitator (active-passive cluster; standby instance used if primary is unreachable). The facilitator signs and broadcasts to the chain RPC.
-
Await confirmation: poll for on-chain confirmation up to the chain-specific timeout.
-
On success:
- Update last_pull_at and last_pull_tx_id on the mandate record
- Increment total_pulled_microunits
- Calculate and store the new next_due_at
- Write a payment_ledger audit chain row
- Write a negotiation_trace_events row with event_type = mandate_activated (for first pull) or subscription_charged
- Emit mandate.charged or mpp_subscription.charged webhook event
- Emit a settlement_attestation receipt (per draft-hopley-x402-settlement-attestation)
-
On confirmation failure (chain timeout or RPC error):
- Record the failed attempt with failure_reason and attempt_count
- Schedule a retry per the pull-retry schedule (see below)
-
On final failure (all retries exhausted):
- Set pull_failed_at and pull_failure_reason on the mandate record
- Emit mandate.charge_failed or mpp_subscription.charge_failed webhook event
- next_due_at is advanced to the next period (the missed period is not retried indefinitely)
- Write a negotiation_trace_events row with event_type = rejected and outcome_reason = pull_failure
Pull-Retry Schedule
| Attempt | Delay Before Retry |
|---|
| 1 (initial) | Immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 (final) | 8 hours |
After six failed attempts, the pull is marked as permanently failed for that period. The mandate remains active; the next period’s pull will be attempted at next_due_at.
Failover
The pull executor submits through the facilitator API. If the primary facilitator is unreachable, the executor retries against the standby instance.
Receipt Chain Integration
Every mandate pull emits a full receipt suite. Receipts are formatted per the AlgoVoi IETF Internet-Draft suite, canonicalised via RFC 8785 JCS, and written to the appropriate audit chains.
Receipts Emitted Per Pull
| Receipt Type | Internet-Draft | Trigger | Key Fields |
|---|
| Settlement attestation | draft-hopley-x402-settlement-attestation | Every confirmed pull | settlement_status (SETTLED / PENDING_FINALITY / REVERSED), tx_id, chain, asset_id, amount_microunits, settled_at |
| Cancellation receipt | draft-hopley-x402-cancellation-receipt | On mandate cancellation or revocation | cancellation_reason (USER_REQUESTED / MERCHANT_REQUESTED / COMPLIANCE_TERMINATED / EXPIRED), cancelled_at, payer_address |
| Refund receipt | draft-hopley-x402-refund-receipt | When a PSD2 refund is owed | refund_type (FULL / PARTIAL / REJECTED), refund_amount_microunits, original_tx_id, refund_tx_id |
Audit Chain Rows Per Pull
| Chain | Row Written | When |
|---|
| payment_ledger | One row per confirmed pull | At pull confirmation |
| negotiation_trace_events | One row per pull attempt (success or failure) | At submission and at outcome |
| compliance_events | One row per state transition | When mandate status changes (activation, pause, revoke, cancel, expire) |
All rows are hash-chained and shipped to off-VM COMPLIANCE-mode Object Lock storage within approximately five minutes of write.
API Endpoints
Mandate Management
| Method | Path | Description |
|---|
| POST | /v1/mandates | Create a new mandate. Returns mandate_id and the on-chain signing URL. |
| GET | /v1/mandates | List all mandates for the authenticated tenant |
| GET | /v1/mandates/ | Get mandate detail including pull history |
| POST | /v1/mandates//pause | Pause an active mandate |
| POST | /v1/mandates//resume | Resume a paused mandate |
| POST | /v1/mandates//cancel | Cancel a mandate; cancel_reason required in body |
Standing Authorities (Tier 2)
| Method | Path | Description |
|---|
| POST | /v1/recurring | Create a new standing authority |
| GET | /v1/recurring | List all standing authorities for the tenant |
| GET | /v1/recurring/ | Get authority detail and pull history |
| POST | /v1/recurring//pull | Trigger an on-demand pull |
| POST | /v1/recurring//pause | Pause |
| POST | /v1/recurring//resume | Resume |
| POST | /v1/recurring//cancel | Cancel |
MPP Subscription (Tier 1)
| Method | Path | Description |
|---|
| GET or POST | /mpp/sub// | Returns 402 with subscription intent body if no active subscription; returns 200 with subscription status if already subscribed |
| POST | /mpp/sub///sign | Receives the on-chain signature credential from the payer application |
| GET | /mpp/sub///status | Returns subscription status fields |
Live Demo Resource
A live MPP subscription fixture is available at:
GET /mpp/sub/demo/mpp-sub-test-daily-base
This resource is permanently maintained and can be used by integration partners to test MPP subscription flows against the live Recurr service without creating a tenant account. It uses Base chain USDC. Do not delete this resource.
Chain Coverage
| Chain | Tier 1 MPP Subscription | Tier 2 Standing Authority | Mandate (basic) |
|---|
| Algorand | Supported | Supported | Supported |
| VOI | Supported | Supported | Supported |
| Hedera | Supported | Not supported | Supported |
| Stellar | Supported | Not supported | Supported |
| Base | Supported | Supported | Supported |
| Solana | Supported | Supported | Supported |
| Tempo | Supported | Not supported | Supported |
Algorand, VOI, Base, and Solana have the richest recurring support because their on-chain authorisation credential formats support the pre-authorisation capability required for Tier 2 pull semantics. Hedera, Stellar, and Tempo support Tier 1 and basic mandates via a challenge-response model per pull.
Integration with Broader Protocol Suite
The Recurr engine participates in the AlgoVoi multi-protocol payment stack alongside x402, AP2, A2A, and hosted checkout. All five protocols share:
- The same payment_ledger audit chain (every confirmed payment from any protocol appears in the same chain)
- The same receipt-format suite (settlement attestation, compliance receipt, cancellation receipt, refund receipt)
- The same compliance engine (every payer address screened on every mandate activation and every pull)
- The same facilitator architecture (pull transactions submitted through the active-passive facilitator cluster)
Protocol field in payment_ledger rows distinguishes recurr payments from checkout, x402, mpp, ap2, and a2a payments. All protocol rows are hash-chained together in a single payment_ledger chain, not in separate per-protocol chains.
Regulatory Alignment
| Framework | Alignment |
|---|
| PSD2 Article 64 (right to revoke recurring payment authorisation) | Addressed: user_requested cancellation emits cancellation receipt; mandate revoked on facilitator detection of on-chain revocation |
| PSD2 Article 89 (refund obligations) | Addressed: refund receipt format available; refund_type = FULL / PARTIAL / REJECTED |
| EMR 2011 Regulations 20–21 (safeguarding) | Live: £100/mandate, £300/account, 3 mandate caps enforced at control plane level |
| UK MLRs 2017 Regulation 40 (transaction record-keeping) | Addressed: payment_ledger rows in COMPLIANCE-mode Object Lock to 2033 |
| MiCA Art. 80 / AMLR Art. 56 (crypto-asset record-keeping) | Addressed: settlement attestation covers record-keeping field requirements |