Agreements
An agreement records what a cardholder allowed you to do with a stored card: who the customer is, what the card may be used for, the limits, the evidence of consent, and the references PayLolly needs to make later merchant initiated payments correctly. Agreements work alongside tokens: a token still identifies the card, an agreement says what was agreed for it.
You do not have to use agreements. Payments with a token and traceId keep working exactly as before. Agreements are for merchants who want recorded consent, several independent arrangements on one card, or consent shown on the hosted page.
On this page
- Vocabulary
- Ways to create an agreement
- Fields by channel
- What an agreement does to a payment
- Lifecycle
- Use Cases
- Rejected requests
Vocabulary
| Term | Meaning |
|---|---|
| agreement | The recorded arrangement for a customer and a card |
agreement |
The object you send on a payment to register one |
cardHolder.reference |
Your id for the customer. It is the customer of the agreement (mandatory when you send agreement, 1 to 64 characters, not case sensitive) |
agreementRef |
Your reference for the agreement, up to 36 characters (letters, digits, -, _, .). It tells parallel agreements of the same customer and card apart |
agreementId |
PayLolly's id of the agreement, returned to you. Quote it on later payments |
| consent | The cardholder's agreement to the arrangement, with its evidence |
An agreement is identified by customer + card + agreementRef + type (the type follows from the request: a request with a schedule is a scheduled agreement, any other an unscheduled one). The agreementRef is not case sensitive and the first spelling is kept. Registering the same combination again updates the same agreement (the agreementId never changes); a different combination creates another agreement. So one card can carry several agreements, for example a wallet top-up and a subscription.
Ways to create an agreement
| Way | Consent | Notes |
|---|---|---|
| Hosted payment | Given on the PayLolly page and recorded by PayLolly | Mandatory or optional (AskTokenize). Also for saved cards that have no consent yet |
| Async payment | You collect it and attest it in the request | consent object mandatory |
Agreement/RecordConsent |
You collect it and attest it | For tokens created before agreements, or imported. No payment involved |
| Sync payment | not supported yet | A sync payment with recurrence OneClick still returns a token as before; add the agreement afterwards with RecordConsent |
All of them are for the registering payment: a customer present payment with recurrence OneClick (or Scheduled for a scheduled agreement) and initiation CIT. 3D Secure always asks the cardholder to authenticate (a mandated challenge, 04) on that payment.
Fields by channel
M = mandatory, O = optional, - = not sent by you (PayLolly records or derives it).
| Field | Hosted | Async | RecordConsent |
|---|---|---|---|
cardHolder.reference (customer) |
M | M | M |
agreementRef |
O | O | O |
description (up to 200) |
M (shown in the consent sentence) | O | O |
maxAmount (greater than 0) |
O (shown) | O | O |
expiresAt (future, UTC) |
O (shown) | O | O |
consent.textVersion (up to 32) |
not allowed | M | M |
consent.timestamp (UTC, not in the future) |
not allowed | M | M |
consent.ip (up to 45) |
- (observed) | O (default: the browser IP seen during 3D Secure) | O |
consent.evidenceRef (up to 64) |
not allowed | O | O |
token |
- | - | M (your token) |
On hosted payments consent is recorded by PayLolly (AGR05 if you send it). On the other channels it is your assertion, so keep your own record of it and quote it in evidenceRef.
The merchant must be enabled for agreements (AGR09).
What an agreement does to a payment
- Later payments: on a payment with a token you may send
agreementId. PayLolly then uses the trace id and processor references of that agreement instead of the token's. With only the token (andtraceId) nothing changes. The card itself is not changed by such a payment: its own trace id and processor references stay as they were, so payments that quote nothing are not affected.agreementIdneeds the stored card (tokenData, otherwiseAGR16) and is not accepted on hosted payments (AGR17): the hosted page finds the agreement of the card from theagreementobject. - Hosted lookup: the cards PayLolly offers a returning customer (
LookupTokensByReference) are only those with an active agreement with recorded consent. See Saved cards. - Consent again: on the hosted page a saved card shows the consent checkbox when its agreement has no recorded consent, or when what you now send differs from what the agreement holds (description, maximum amount, end date or schedule). A card whose agreement says exactly the same is not asked again.
- Scheduled agreements: a payment that quotes a scheduled agreement is compared with its schedule (amount, interval, instalments). Deviations are counted, never refused.
- Never blocking: a payment is never rejected because an agreement is missing, cancelled or expired. A payment under a cancelled or expired agreement is counted and, where you quote it, still processed. This keeps your existing merchant initiated payments working. An
agreementIdthat is not yours, or does not belong to the card of the payment, is the exception: it is rejected withAGR08, because it is a mistake in the request.
Lifecycle
| State | How | Effect |
|---|---|---|
| Created | A registering payment is approved (or RecordConsent) |
agreementId returned |
| Updated | The same customer, card, type and agreementRef again |
Same agreementId. New consent replaces the terms (description, maximum amount, end date, schedule: what you leave out is cleared), and the previous consent with the terms it covered stays in the agreement's record (the last 10). A cancelled agreement becomes active again only with new consent. A scheduled agreement starts a new series: the payment count restarts and the dates are counted from the registering payment |
| Cancelled | Agreement/Cancel |
Only a flag: it is no longer offered to the cardholder on the hosted page, and later payments quoting it are counted. Nothing else is blocked |
| Read | Agreement/Get |
Returns the agreement, its consent evidence and the masked card. Changes nothing |
| Deleted | With its card | A card unused for two years is deleted together with its agreements and their consent evidence. See Token service |
Use Cases
1. Hosted: register a card, consent mandatory
recurrence OneClick, mode New, cardHolder.reference and an agreement with a description. The cardholder must tick the consent box. Full request, pictures and response: Hosted Payment.
2. Hosted: register a card only if the cardholder agrees
recurrence OneTime and mode New,AskTokenize. Ticked: the card is registered. Unticked: a normal one-time payment, response NotCreated / ConsentDeclined. See Hosted Payment.
3. Hosted: returning customer with a saved card
mode PresetToken, PresetTokens or New,PresetToken with the same agreement. The consent box appears only while the agreement has no recorded consent for that card. See Hosted Payment.
4. Async: register a card with your own consent
You collect the consent on your page and tell PayLolly what the cardholder agreed to. PayLolly runs 3D Secure and registers the agreement after approval. Details and the 3D Secure flow: Async Payment.
"agreement": {
"agreementRef": "TOPUP-1",
"description": "Wallet top-ups",
"maxAmount": 100,
"consent": {
"textVersion": "v3",
"timestamp": "2026-10-16T09:00:00Z",
"evidenceRef": "consent-2026-10-16-4711"
}
}
The result is in GetPaymentStatus and the webhooks:
"agreement": { "agreementId": "0198ab5c-6f37-7a02-9d7e-3a5e2c1f4b10", "status": "Created", "agreementRef": "TOPUP-1", "consentStatus": "Recorded" }
5. Later payment with agreementId, and the older way side by side
Both work. Store the agreementId and the token when the card is registered.
With the agreement (the agreement's trace id and processor references are used):
{
"merchantId": "dbdab964-82ee-4b09-a26b-68403c31afa7",
"accountId": "81de44cb-4b70-4207-9f5e-54355491a5cc",
"currency": "EUR",
"amount": 10,
"merchantRef": "9e31818e-407b-48d8-8bbe-eb19fa565733",
"tokenData": { "token": "08524FF1F8BE79B7EA0CE19204E81BF2" },
"agreementId": "0198ab5c-6f37-7a02-9d7e-3a5e2c1f4b10",
"authorisationType": "AuthCapture",
"recurrence": "Unscheduled",
"initiation": "MIT"
}
The older way, still valid (token and traceId of the registering payment):
{
"merchantId": "dbdab964-82ee-4b09-a26b-68403c31afa7",
"accountId": "81de44cb-4b70-4207-9f5e-54355491a5cc",
"currency": "EUR",
"amount": 10,
"merchantRef": "9e31818e-407b-48d8-8bbe-eb19fa565734",
"tokenData": { "token": "08524FF1F8BE79B7EA0CE19204E81BF2", "traceId": "0102030405060708090A0B0C0D0E0F" },
"authorisationType": "AuthCapture",
"recurrence": "Unscheduled",
"initiation": "MIT"
}
An agreementId is needed when one card has several agreements and you want a payment made under a particular one. It must belong to your merchant and to that token, otherwise AGR08. See Payment Actions.
6. Record consent for a card you already hold
For a token created before agreements existed (or imported with TokenizeCard), when the cardholder gave consent in your own journey.
curl -X 'POST' \
'https://api.dev.paylolly.app/api/rest/v1.0/Agreement/RecordConsent' \
-H 'accept: application/json' \
-H 'MerchantKey: 35236fcf-12f4-4c94-b4ba-5d236457b68a' \
-H 'Content-Type: application/json' \
-d '{
"token": "08524FF1F8BE79B7EA0CE19204E81BF2",
"cardHolder": { "reference": "CUST-1" },
"agreement": {
"agreementRef": "TOPUP-1",
"description": "Wallet top-ups",
"consent": { "textVersion": "v3", "timestamp": "2026-09-01T09:00:00Z", "ip": "203.0.113.9", "evidenceRef": "consent-2026-09-01-4711" }
}
}'
{
"agreementId": "0198ab5c-6f37-7a02-9d7e-3a5e2c1f4b10",
"status": "Created",
"agreementRef": "TOPUP-1",
"consentStatus": "Recorded"
}
- The consent time may be in the past (when it was given), never in the future.
- The same call again (same token, customer and
agreementRef) returnsUpdatedwith the sameagreementId; a differentagreementRefcreates another agreement.RecordConsentregisters unscheduled agreements: a scheduled agreement is registered and changed only by a payment (agreement.schedulehere givesAGR11). When you sendmaxAmountalso sendcurrency(one of your account currencies, with the decimals it allows;AGR06otherwise).consent.ipmust be an IP address (AGR07). - PayLolly copies the token's trace id and processor references into the agreement when it is created (or when it holds none), so it keeps working even if the token's own values change later. A repeat of the call never replaces the references of the agreement's own registering payment.
- The token must be yours (
TOK07otherwise). The call has no effect on payments that use only the token. - This is also how you make cards saved before agreements existed appear again in the hosted lookup.
7. Cancel an agreement
curl -X 'POST' \
'https://api.dev.paylolly.app/api/rest/v1.0/Agreement/Cancel?agreementId=0198ab5c-6f37-7a02-9d7e-3a5e2c1f4b10' \
-H 'MerchantKey: 35236fcf-12f4-4c94-b4ba-5d236457b68a'
{ "agreementId": "0198ab5c-6f37-7a02-9d7e-3a5e2c1f4b10", "status": "Cancelled" }
The agreement is flagged and no longer offered on the hosted page. Payments that quote it are counted and still processed (the cardholder's withdrawal of consent is your responsibility to honour: do not initiate payments under an agreement you cancelled). An unknown id, or one of another merchant, gives AGR08.
8. Get an agreement
Read an agreement back: what it allows, the consent evidence held for it, and the card it applies to (masked). Use it to check that consent is on record before you start charging, to answer a customer or a dispute, or to see whether the scheme reference is held.
curl -X 'GET' \
'https://api.dev.paylolly.app/api/rest/v1.0/Agreement/Get?agreementId=0198ab5c-6f37-7a02-9d7e-3a5e2c1f4b10' \
-H 'accept: application/json' \
-H 'MerchantKey: 35236fcf-12f4-4c94-b4ba-5d236457b68a'
{
"agreementId": "0198ab5c-6f37-7a02-9d7e-3a5e2c1f4b10",
"status": "Active",
"agreementType": "Unscheduled",
"agreementRef": "TOPUP-1",
"customerReference": "CUST-1",
"description": "Wallet top-ups",
"maxAmount": 100,
"currency": "EUR",
"expiresAt": "2027-12-31T00:00:00Z",
"maskedPAN": "400000*0002",
"tokenExpiresAt": "2030-12-31T23:59:59.9999999Z",
"registrationPaymentId": "8bea8040-dee7-441c-a35f-d6ce47d6c7f3",
"schemeReference": "Present",
"consent": {
"status": "Recorded",
"source": "HostedPage",
"textVersion": "hosted-v1:3f6a1c9d",
"timestamp": "2026-10-15T09:00:00Z",
"ip": "203.0.113.9"
},
"threeDS": {
"version": "2.2.0", "eci": "5", "transStatus": "Y",
"method": "02", "authenticatedAt": "2026-10-15T09:00:03Z",
"dsTransId": "6b3f2a1e-3c52-4d0e-9a5b-1f2d3c4b5a69", "acsTransId": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d"
},
"createdAt": "2026-10-15T09:00:04Z",
"lastUpdated": "2026-10-15T09:00:04Z",
"lastUsedAt": "2026-10-15T09:00:04Z"
}
| Field | Meaning |
|---|---|
| status | Active, or Cancelled (see Cancel) |
| agreementType | Unscheduled, or Scheduled (see use case 10) |
| schedule, paymentsMade, nextPaymentDue | Scheduled agreements only: the agreed amount and interval, the approved payments with an amount made under it, and when the next one is expected (a date; empty when complete, cancelled or past its end). You make the charge, PayLolly only shows the expectation |
| customerReference | Your cardHolder.reference, upper cased and trimmed |
| maxAmount, currency, expiresAt | The limits you agreed with the cardholder, when there are any |
| maskedPAN | The card, first 6 and last 4 digits. The card number is never returned |
| tokenExpiresAt | The last moment the card can be used: the end of its expiry month (UTC). Worked out from the card expiry, not stored |
| lastUsedAt | When the agreement was last used: the registering payment, or a later approved payment that quoted the agreementId. Declined payments, payments that do not quote it, and RecordConsent do not count. Not present when never used |
| lastUpdated | When the agreement itself last changed (the same customer, card and agreementRef registered again, or cancelled) |
| registrationPaymentId | The payment that registered the agreement. Not present when the agreement came from RecordConsent |
| schemeReference | Present, or Missing when the processor returned no scheme reference (Trace ID) for the registering payment. The agreement and its consent are recorded either way, but a later merchant initiated payment cannot then be linked to the original payment by the scheme, and the issuer may decline it |
| consent.status | Recorded, or LegacyUnrecorded when no consent evidence is held |
| consent.source | HostedPage (the cardholder ticked the box on the hosted page), MerchantAttested (you recorded it) or None |
| consent.textVersion, timestamp, ip, evidenceRef | The evidence: the version of the text shown, when it was given, the cardholder's IP when known, and your own reference |
| threeDS | Version, ECI and transaction status of the registering payment, when there was one (never the cryptogram), and how the cardholder was authenticated: method (01 frictionless, 02 challenge), authenticatedAt (UTC), dsTransId and acsTransId. If you run 3D Secure yourself for a later customer present payment, send them as requestorPriorAuthenticationInfo (method, timestamp as yyyyMMddHHmm, threeDSReqPriorRef = acsTransId, threeDSReqPriorDsTransId = dsTransId; the last one only on 3DS 2.3.1). PayLolly does this itself for async and hosted payments, see 3D Secure configuration |
| threeDSRecurring | Scheduled agreements: the recurring 3D Secure fields derived from the schedule (authenticationInd, recurringExpiry, recurringFrequency, purchaseInstalData), to use when you authenticate the registering payment yourself |
An unknown id, or one that belongs to another merchant, gives AGR08. Merchants without agreements enabled get AGR09.
9. Find agreements in the Portal and through MCP
The Portal has an Agreements page (menu item Agreements) for administrators and for users with the Agreements role. It is read only. The merchant is the one selected in the top bar; without one, an administrator sees every merchant and any other user the merchants they are assigned to. Filters:
- agreement id, customer reference (case and surrounding spaces ignored), agreement ref
- status, consent status and consent source, only agreements without a scheme reference
- created from / to, not used since (never used counts), card first 6 and last 4, card expires before
Each row shows the customer, agreement ref, status, masked card, when the card expires, consent, created and last used. Details shows, for a scheduled agreement, the schedule with the payments made and the next expected payment, and the consent evidence (text version, time, IP, your evidence reference), the registering payment and the 3D Secure result of the registering payment. The customer reference is shown in full. The card number, the scheme reference itself, processor references and 3D Secure identifiers are never shown.
The same data is available to MCP clients with two read only tools, with the same role and merchant rules: search_agreements (the same filters, newest first, paged; the agreement reference is matched without regard to case; filters on the type, consent and scheme reference read the agreements and are refused above 5,000 matches, the others page in the database at any size) and get_agreement (one agreement with its consent evidence). Neither can change or cancel an agreement.
10. Scheduled agreements: subscriptions and instalments
A scheduled agreement has a fixed amount and a fixed interval, for example 10.00 EUR every month for 12 months. It is registered like the others, with a customer present payment, but with recurrence Scheduled and an agreement.schedule. The cardholder agrees to exactly that schedule (on the hosted page the sentence they tick is generated from the schedule fields).
Note
You collect every payment. PayLolly does not schedule or charge anything: each payment is a merchant initiated sync payment (initiation MIT, recurrence Recurring) that quotes the agreementId, sent by you when it is due. The schedule is what the cardholder agreed to; PayLolly records it, checks your payments against it and shows what it expects next.
Warning
Scheduled agreements are only accepted for accounts whose processor was verified for them. Until then a request with recurrence Scheduled is rejected with RTE04. Ask PayLolly which of your accounts are enabled.
Registering (async shown, hosted is the same without consent):
{
"currency": "EUR",
"amount": 10,
"merchantRef": "2425363c-5541-4c37-a698-77447650610d",
"cardHolder": { "reference": "CUST-1", "email": "test@test.com", "name": "test user" },
"authorisationType": "AuthCapture",
"recurrence": "Scheduled",
"initiation": "CIT",
"agreement": {
"agreementRef": "GYM-1",
"description": "Gym membership",
"expiresAt": "2027-11-30T00:00:00Z",
"schedule": {
"amount": 10,
"interval": { "unit": "Month", "count": 1 },
"firstPaymentDate": "2026-12-01",
"instalments": 12,
"cancellationUrl": "https://shop.example/cancel"
},
"consent": { "textVersion": "v4", "timestamp": "2026-11-15T09:00:00Z" }
}
}
| Field | Rules |
|---|---|
schedule.amount |
The fixed amount of every payment, greater than 0, in the payment currency. maxAmount is not allowed together with a schedule (AGR12): a price change is a new agreement with a new consent |
schedule.interval |
unit Day, Week, Month or Year, and a count of 1 to 999 units between payments |
schedule.firstPaymentDate |
Optional, today or later. Without it the registering payment is the first payment and its amount must equal schedule.amount. With it, the registering amount may be anything, 0 for a free trial or a card check. A zero amount requires firstPaymentDate (AGR18): a card check is not a payment, so the start of the schedule has to be stated. Dates without a time zone (2026-12-01) are UTC dates |
schedule.instalments |
Optional, 2 to 999 payments. Without it the agreement runs until expiresAt |
agreement.expiresAt |
Optional when instalments is given. One of the two is required (AGR13), and when both are sent the instalments must fit before expiresAt A schedule runs at most 10 years from its first payment (AGR13). |
schedule.cancellationUrl |
Optional, https, up to 200 characters. Shown to the cardholder on the hosted page |
agreement.consent |
As for async. Not allowed on hosted, where PayLolly records the tick |
On the hosted page the consent is always mandatory (the AskTokenize mode is rejected with AGR15), and a saved card is asked again when the schedule differs from the one its agreement holds (the same rule as for every agreement). The sentence is built from your fields, for example:
I agree that my card details are saved and EUR 10.00 will be charged every month, starting 2026-12-01, for 12 payments, until 2027-11-30. Gym membership I can cancel at https://shop.example/cancel
The text version stored as evidence is hosted-s1: followed by a short hash of the exact sentence shown.
Later payments are ordinary merchant initiated payments:
{
"merchantId": "dbdab964-82ee-4b09-a26b-68403c31afa7",
"accountId": "81de44cb-4b70-4207-9f5e-54355491a5cc",
"currency": "EUR",
"amount": 10,
"merchantRef": "9e31818e-407b-48d8-8bbe-eb19fa565735",
"tokenData": { "token": "08524FF1F8BE79B7EA0CE19204E81BF2" },
"agreementId": "0198ab5c-6f37-7a02-9d7e-3a5e2c1f4b10",
"authorisationType": "AuthCapture",
"recurrence": "Recurring",
"initiation": "MIT"
}
Scheduled itself is only for the registering payment: a merchant initiated payment with recurrence Scheduled is rejected (VAL08), use Recurring.
Checks when you charge. A payment under a scheduled agreement is compared with what was agreed. A payment is never refused for it; each deviation is counted so that it can be followed up, and enforcement can later be switched on per merchant:
| Deviation | Counted when |
|---|---|
| amount | the amount is not the schedule.amount |
| currency | the currency is not the agreement currency |
| too early | the payment comes more than 3 days before its due date: the first payment date (or the registering payment) plus one interval for each payment made. A late payment does not make the next one early, and a card check does not move the due date |
| before the first payment | the firstPaymentDate has not arrived |
| instalments | all instalments were already paid |
| cancelled, expired | as for every agreement |
A zero amount card check is never compared.
Following the schedule. Agreement/Get (and the Portal and MCP) show the schedule, paymentsMade and nextPaymentDue. paymentsMade counts approved payments with an amount made under the agreement, including the registering payment when it was the first payment. nextPaymentDue is worked out from the first payment date (or the registering date) plus the payments made times the interval, so a late payment does not move the following dates. It is empty when the agreement is complete, cancelled or past its end.
Rejected requests are listed below.
Rejected requests
| Request | Error |
|---|---|
initiation MIT or MOTO on an async or hosted payment |
VAL13 (these payments are always cardholder initiated) |
initiation MIT with recurrence OneClick or OneTime |
VAL08 ("a merchant initiated payment cannot be OneClick, use Unscheduled or Recurring") |
initiation MOTO with any recurrence other than OneTime |
VAL08 ("a MOTO payment must be OneTime") |
agreement without cardHolder.reference (or longer than 64) |
AGR01 |
agreement with a recurrence other than OneClick (hosted: or OneTime with AskTokenize) |
AGR02 |
hosted without agreement.description |
AGR03 |
async or RecordConsent without a valid consent (textVersion and a timestamp not in the future) |
AGR04 |
hosted with agreement.consent |
AGR05 |
maxAmount not positive or too many decimals, expiresAt not in the future |
AGR06 |
recurrence Scheduled without agreement.schedule (or without the agreement object) |
AGR10 |
agreement.schedule with a recurrence other than Scheduled |
AGR11 |
bad schedule amount, interval or instalments; maxAmount together with a schedule; a registering amount that is neither the schedule amount nor 0 when there is no firstPaymentDate |
AGR12 |
neither expiresAt nor instalments, instalments that do not fit before expiresAt, or a schedule that runs more than 10 years from its first payment |
AGR13 |
firstPaymentDate in the past |
AGR14 |
hosted Scheduled with AskTokenize |
AGR15 |
agreementId without tokenData |
AGR16 |
agreementId on a hosted payment |
AGR17 |
initiation MIT with recurrence Scheduled |
VAL08 |
Scheduled and no account of the merchant supports it |
RTE04 |
schedule.cancellationUrl not https or longer than 200 |
AGR07 |
invalid agreementRef format, or a field longer than allowed |
AGR07 |
unknown agreementId, or one of another card or merchant |
AGR08 |
| agreements not enabled for the merchant | AGR09 |
| token not yours or invalid | TOK07 |
Example (AGR01):
{
"type": "AGR01",
"title": "Agreement requires cardHolder.reference (1 to 64 characters)",
"status": 500,
"traceId": "00-e0851274983d581c7837f1a7ae895e99-86117ae4f235f4f9-00"
}
Every code is in Errors.