Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

The lifecycle of a subscription

A subscription has one status at a time. It is what GET /v1/access and every webhook carry.
Each periodactivea charge is dueactivenext periodunpaidMesub retriesactiveback on schedulestoppedaccess offpaidnot paidpaidnot paidAfter a cancellationcancelledaccess to period endactivecharged againendedaccess offresumedperiod over

Statuses

statusaccesspayment_statusWhat it means
nonefalsenoneThis customer never subscribed to this plan. Not an error
pendingfalsenoneReserved, waiting for the wallet's transaction to land
activetruepaidPaid up. The next charge is at next_charge_at
unpaidtrue, false on FreelateA charge missed. A retry is coming
cancelledtrue until the endpaidThe customer cancelled. The period already paid runs to access_until
stoppedfalsenoneCharging stopped, most often when the retries ran out. stop_reason says why
endedfalsenoneOver. end_reason says why
failedfalsenoneWhat landed on chain is not what Mesub reserved, so it is not billed. Rare
supersededfalsenoneReplaced by a newer subscription of the same wallet to the same plan
ended, failed and superseded are final. stopped is not: Mesub charges nothing more, but the wallet's approval stays open on Solana until the customer cancels then closes it, and until then they can come back.
Gate on access, not on status: a cancelled subscription still grants access for a while, an unpaid one may or may not, and an active one has none once its plan has ended.
A checkout nobody signed turns expired. It never appears in /v1/access. It does in GET /v1/subscriptions, when you read a subscription by its id, and in the subscription.expired webhook. Only a superseded one is left out of the list.

Late payments

When a charge misses, most often because the wallet holds less than the price, the subscription turns unpaid and payment_status reads late.
late_reason says why, as the last failed charge found it. A stopped subscription keeps it too, for the payment it stopped on. It is null on every other status, and when Mesub cannot name the cause.
late_reasonWhat happenedWhat fixes it
insufficient_balanceThe wallet holds less than the priceAdding funds
approval_revokedThe wallet no longer lets Mesub charge itNot adding funds
authority_closedThe wallet closed its authorisation, for goodNothing
An approval is revoked in the wallet itself, or replaced when another app is approved on the same token. Read late_reason before you tell a customer to top up. Every charge and retry is kept with its own outcome and reason: read an attempt says what each means.

On Dev and Business

Mesub retries on its own. A plan gets 3 retries by default, so a period has 4 tries in all. Each retry waits a quarter of the period, one day at most:
Plan periodWait between tries
1 month1 day
1 week1 day
1 day6 hours
1 hour15 minutes
Access stays on while retries run. next_retry_at says when the next one is, next_retry_number and retries_allowed say which one it is, and access_until is the date of the last retry: if that one fails too, the subscription turns stopped and access ends.
A retry that pays turns it back to active. On insufficient_balance the customer has nothing to sign: they only need to add funds.
You can change the number of retries and the wait between them per plan, in the plan's Retry policy in the dashboard. All the retries must fit inside one period.

On Free

There are no automatic retries, and an unpaid subscription grants no access. You can retry by hand, up to 3 times, until retry_deadline: from the dashboard, or from your own page with a retry.
retry_deadline is two minutes before the missed period ends. Past it, a retry is refused with retry_deadline_passed. The subscription turns stopped a few minutes later, once the missed period is over: for instance a deadline at 15:15 stops it around 15:19, after the period ends at 15:17.

Paying now

On any tier, a late payment can be paid at once rather than at its next retry, for a customer who just topped up: from the dashboard, or from your server with subscriptions.retry, which the widget shows as Pay now. Pay a late payment now has the detail.

Why a subscription stops

A stopped subscription carries stop_reason. It is null on every other status, and on one that stopped before Mesub recorded why.
stop_reasonWhat happenedWhat the customer can do
out_of_retriesEvery retry of the missed payment failedCome back, or cancel then close
retry_deadline_passedOn Free, the last day to pay the missed payment passedCome back, or cancel then close
authority_closedThe wallet closed its authorisation for good. Nobody can charge itClose it at once (closable is true)
One stopped with authority_closed cannot come back: the customer closes it, then subscribes anew.

Blocked charges

A charge can also fail for a reason that is not the customer's. The subscription is then blocked: its charges are held, and the customer is never counted late for it.
The status does not change. A blocked subscription stays active, or unpaid if it was late already, and payment_status stays as it was. Two fields say it instead, on GET /v1/access and on the subscription: blocked_since, the date the block began, and blocked_reason. Both are null on a subscription that is not blocked.
blocked_reasonWhat happenedWhat it asks of you
puller_not_allowedMesub's puller was taken off the plan on chainAdd Mesub back to the plan's pullers, with the wallet that owns the plan
receiver_not_allowedThe receiving wallet is not among the plan's destinationsChoose a receiving wallet that is among them
receiver_account_missingIt has no account for the plan's token, and nobody else can be paidOpen an account for that token
receiver_account_frozenThe token account it is paid into is frozenHave it thawed, or choose another receiving wallet
receiver_account_needs_memoThat token account requires a memo on every transfer inTurn the memo requirement off, or choose another receiving wallet
receiver_account_confidential_onlyThat token account takes confidential transfers onlyAllow plain transfers into it, or choose another receiving wallet
mint_goneThe plan's token no longer existsCreate a new plan with another token
mint_pausedThe token's issuer paused itNothing: Mesub charges again once the issuer lifts the pause
mint_transfer_hookThe token's issuer made its transfers run a program of theirsNothing, if the issuer removes it. Otherwise a new plan in another token
mesubAny cause on Mesub's sideNothing
The first seven are your plan's own setup, and only you can undo them. mint_paused and mint_transfer_hook are the token issuer's: only the issuer lifts them. Mesub mails you when your setup or the issuer holds your charges. mesub covers every other cause, whatever it is.
Mesub keeps trying: about every ten minutes, then less often, at least once a day, once a subscription blocked by the plan's setup has lost its access. next_charge_at is the next try, or next_retry_at on a subscription that was late already. The first charge that goes through, or that the chain holds as paid, lifts the block. Access comes back with it, and the customer has nothing to sign.
Access has an end all the same, and access_until is its date:
blocked_reasonAccess lasts
mesubTo the end of the period after the one paid for
Any otherPast the paid period, as long as the plan's retries give a late payment
It is never less than a few minutes after the end of the paid period, which is what a plan with no retry, and any plan on Free, gets. It is never later than the end of the period after the one paid for. access is never true past access_until on a blocked subscription.
blocked_since is the date of the block blocked_reason names: when a cause on Mesub's side gives way to one of your setup, or back, it starts again.

Cancelling and resuming

The customer cancels by signing one transaction with the wallet that pays. It moves no money: it records on chain that the subscription ends with the period already paid.
FieldBeforeAfter cancelling
statusactivecancelled
accesstruetrue until access_until
cancelled_atnullWhen they cancelled
next_charge_ata datenull
Until access_until, they can resume: one more signature, and the subscription is active again with its next charge back on schedule.
After access_until, it turns ended with end_reason: "cancelled". The customer can then close it to get the rent of its account back.

When the plan has an end date

A plan can carry an end date, set when you create it. ends_at on the plan says when, and is null on a plan with no end.
Access stops at the plan's end for everybody. access is false from that moment on, for a subscription that is active, unpaid, cancelled or parked alike, and access_until is never later than the plan's end.
The last period is charged in full, even when the plan ends inside it. Mesub does not prorate it, and paying it does not carry access past the plan's end. The terms the customer signs when subscribing say both.
Once the plan ends before the next charge, nothing more is charged, and the dates say so:
FieldWhat it reads near the plan's end
access_untilThe plan's end, when the paid period or the retries run past it
next_charge_atnull: a subscription in its last period has no next charge
next_retry_atnull when the plan ends before the retry
retry_deadlineOn Free, the plan's end when it comes before the deadline
At the plan's end, a subscription that is active or unpaid turns ended with end_reason: "plan_ended", and subscription.ended is sent. One the customer had cancelled ends with cancelled, as it would have.
Mesub ends it within a few minutes of the plan's end. For that long status can still read active or unpaid while access is already false. It never reads ended with access true. A guard reads access, never status.
From its end on, a plan takes no new subscriber either: creating a subscription to it is refused with plan_ended.

Why a subscription ends

An ended subscription carries one end_reason:
end_reasonWhat happened
cancelledThe customer cancelled and the paid period ran out
plan_endedThe plan reached its own end date
plan_removedYou deleted the plan
plan_replacedAnother plan now stands at its address
closed_elsewhereIt was closed on chain, outside Mesub
closedThe customer closed the subscription through Mesub
It is null on every other status.

Parked seats

A project holds a limited number of subscribers on Free and Dev. After a downgrade, the seats over the limit are parked: paused is true, the status stays as it was, nothing is charged, and access runs to the end of the period already paid. Moving back up a tier puts them back on schedule.

Coming back

A customer whose subscription stopped or ended can subscribe again to the same plan, with the same wallet. The new subscription takes over. A stopped one turns superseded; an ended one stays as it was. A stopped subscription holds no seat, so coming back needs a free one, like any new subscriber: on a full project it is refused with seat_cap_reached.
A return asked within about two minutes of the end of the old subscription's billing period is refused with comeback_period_rolling, so the terms the wallet signs name the period that is really charged. Wait for the Retry-After it carries, then create the subscription again: a transaction built before the wait is not sent.
subscribed_since is the start of the current one, and first_subscribed_at the start of the very first.