Documentation

Membership Lifecycle

Cancel, resume, renew, move, end and restore a membership, as the holder or as the organization.

This page covers changing a membership after it starts. Two parties can act on it: the holder, with their own token, and the organization, with an org-scoped token. They use different paths and have different powers.

All paths are under https://api.davi.social/api/v1.

Check access with the dates, not the status

status is active, cancelled, expired or revoked. It records how a membership ended, not whether it is running. Two nullable dates answer that:

FieldSet byMeans
valid_untilthe termWhen access ends. null never expires
cancelled_atthe holderThe holder gave notice. The membership will not continue past valid_until

status lags in both directions:

  • A membership under notice stays active until it lapses. cancelled_at is the notice; status is the outcome.
  • A membership whose term has just run out still reads active until a sweep reconciles it shortly afterwards: to cancelled if notice was given, otherwise to expired.

Gate access on valid_until, not on status == "active". Gating on the status lets a lapsed membership through until the sweep runs, and locks out a holder who cancelled but has paid time left.

Notice does not withdraw anything. The tier, the membership entitlements and the membership wallet all stay in place until valid_until.

As the holder

These act on the caller's own membership with the caller's own token. There is no user_uuid in the path; the token identifies the holder.

MethodPathScope
GET/membershipsuser:read
GET/memberships/{organization_slug}/tiersmembership:read
GET/memberships/{organization_slug}/historymembership:read
GET/memberships/{organization_slug}/cardsmembership:read
PATCH/memberships/{organization_slug}membership:tier:update
POST/memberships/{organization_slug}/renewmembership:renew
POST/memberships/{organization_slug}/resumemembership:renew
DELETE/memberships/{organization_slug}membership:leave

A caller with no membership in the organization and an organization that does not exist both answer 404.

Cancel, and resume

curl -X DELETE \
  "https://api.davi.social/api/v1/memberships/ORG_SLUG" \
  -H "Authorization: Bearer ACCESS_TOKEN"

The response carries cancelled_at and the valid_until on which access ends. Two kinds of membership end immediately, with valid_until set to now: a one-time membership, which never had an expiry, and one that had already lapsed.

POST /memberships/{organization_slug}/resume withdraws the notice and changes nothing else. It works only on a membership that is still running. A lapsed membership cannot be resumed; the holder joins again instead.

Cancelling twice, or resuming a membership that was never cancelled, answers 409.

Renew

curl -X POST \
  "https://api.davi.social/api/v1/memberships/ORG_SLUG/renew" \
  -H "Authorization: Bearer ACCESS_TOKEN"

The new term is added to the end of the current one, not to the moment of renewal, so renewing early keeps the time left. Renewing also withdraws a cancellation.

A membership stays renewable for two weeks after valid_until, until renewable_until. Renewing inside that window continues the same membership and history. After it, the membership is retired, and coming back is joining again on a fresh term, with the old history left behind.

Only a free membership can be renewed with this call. A membership with a price, whether the tier's or a custom price set for that holder, answers 402 with the amount and currency owed. Send the holder through checkout instead; the new term follows the settled payment.

Move to another tier

Send PATCH /memberships/{organization_slug} with a tier the organization offers openly. The remaining term carries over, converted into the new tier's days by value: it buys fewer days on a more expensive tier and more on a cheaper one. No money moves. The join date, history and membership stay the same.

A tier the organization keeps closed, a tier belonging to another organization, and a tier that does not exist all answer 404.

As the organization

These need an org-scoped token and name the holder by user_uuid.

MethodPathScope
POST/organizations/{organization_slug}/membershipsmembership:join
PATCH/organizations/{organization_slug}/memberships/{user_uuid}membership:tier:update
POST/organizations/{organization_slug}/memberships/{user_uuid}/renewmembership:renew
DELETE/organizations/{organization_slug}/memberships/{user_uuid}membership:leave
DELETE/organizations/{organization_slug}/memberships/{user_uuid}/revocationmembership:join
POST/organizations/{organization_slug}/memberships/{user_uuid}/restoremembership:join

End a membership

DELETE /organizations/{organization_slug}/memberships/{user_uuid} gives a week's notice. The membership keeps granting everything it granted until the week runs out, then it is retired. Pass revoke_immediately to skip the notice.

To call it off within the week, send DELETE /organizations/{organization_slug}/memberships/{user_uuid}/revocation. Nothing was taken away, so nothing needs to be put back.

A pending revocation is visible to the organization only. The organization's membership list carries revoke_at. The holder's GET /memberships has no such field, and no email or event is sent. On a holder-facing page, the only ending you can show is a cancellation the holder made themselves.

Restore an ended membership

After the notice has run out, revocation no longer applies. Use POST /organizations/{organization_slug}/memberships/{user_uuid}/restore instead. It brings back the most recently ended membership as it stood: the term resumes with the time it had left rather than restarting, and a term that expired in the meantime comes back expired.

  • Notice the holder had given stays in place. Withdrawing it is the holder's action.
  • If the holder has rejoined in the meantime, the restore is refused. An organization cannot have two live memberships for the same person.

Enrolling the person again instead starts a term from today, and the history shows them leaving and coming back.

Read the history

GET /memberships/{organization_slug}/history for the holder, or GET /organizations/{organization_slug}/memberships/{user_uuid}/history for the organization. Entries are newest first and cover earlier memberships too: a membership that lapsed and was later rejoined is a separate record. Each entry names the tier it was on, so tier moves appear in sequence.

A pending revocation does not appear in the history, because the membership is still live.

Next