Skip to main content

Outcome

The intended person keeps one exact membership UUID through create, correction, resend, cancellation or revocation, and resource-idempotent re-add. Email is a field, never a resource selector. Only pending memberships can change role, be resent, or be cancelled. Active memberships can be revoked only when paywise enables that capability for the Partner. Re-add uses a new logical command key but reuses a cancelled or revoked membership UUID. Every add returns pending_setup with an invite_expires_at, whether or not the address already belongs to a paywise account. The response echoes the email, first_name, and last_name you submitted until the person accepts the invite; it never reveals whether an account existed or what its profile holds. A person whose account this add created sets a password; every account that existed before — with or without a password, signing in through a social or single sign-on provider, or deactivated — accepts by signing in, so no invitee can set the password of an account someone else provisioned. Only that acceptance makes the membership active and starts paywise notifications for that company — a Partner cannot activate a membership on a person’s behalf, and completing a password setup activates only the membership of the company that sent it. A re-added cancelled or revoked membership is pending_setup again and needs a fresh acceptance. Resend and cancel are bodyless idempotent commands: any request body, even {} or [], is 400 unexpected_body, and a refused command does not claim its idempotency key. A PATCH timeout is not blindly replayed; reconcile the exact membership URL first. The add and resend responses carry setup_url once: the absolute link to the paywise invite page for that pending membership, for embedding in your own onboarding screen or mail. It is absent from reads and from idempotent replays, and null when no invite is open. Persist it immediately if you need it, and treat it as a secret — opening it is how the person activates the membership. Two Partner events close the loop without polling: company.user.activated when the membership becomes active, and company.user.invite_expired when the 10-day invite lapses unaccepted (the membership stays pending_setup; resend to issue a new link). If the person lost the link and their account was created through the Partner API and never used — active, never signed in, no password, no social login — they can also use Passwort vergessen in the paywise portal. The reset link goes only to their own address, never to you; after setting a first password they sign in and accept the invitation. A setup link stops working once its account has been signed in to, linked to a social login, or deactivated; resend the invitation to give a person who can sign in a sign-in link.

1. Reusable sandbox proof

Run this authenticated probe immediately before every Partner write below, including each bounded retry attempt. Require the environment header to equal the exact string sandbox.

2. Add and retain the returned UUID

3. Prove the sandbox before correction

4. Correct the exact pending membership

PATCH is resource-targeted and does not use an idempotency key. Its body is MembershipRoleRequest, and role is required — there is no other editable field on a membership. After an ambiguous outcome, retrieve this exact URL and compare its current role.

5. Prove the sandbox before resend

6. Replace and resend the setup token

This bodyless command invalidates earlier setup tokens. A fresh 200 resend response returns the replacement setup_url once; an idempotent replay omits it. Transfer the URL directly to a restricted secret sink and never log it.
The cURL example keeps the one-time response in permission-restricted membership-resent.json. Move its setup_url into your restricted onboarding flow immediately, then delete the response file.

7. Prove the sandbox before cancellation

8. Cancel the same pending membership

Revoke an active membership

Revocation is deliberately separate from pending cancellation. First call GET /partner/v2/info/ and check that capabilities includes "USER_ACCESS_REVOCATION". Then send a fresh idempotency key and a non-empty audit reason of at most 500 characters to POST /partner/v2/companies/{company_id}/users/{membership_id}/revoke/:
A successful response has status: "revoked" and retains revoked_at, revoked_by_token_id, and revocation_reason. The command clears only that user’s personal grants for this company and repairs their default-company selection. It does not affect their other memberships, the company, or company-owned API credentials. The API returns 409 last_company_admin_required for the final active admin and 409 membership_not_active for pending, cancelled, or already revoked memberships. Pending access must use /cancel/. On a timeout or transient 5xx, retry the exact body with the same key; a replay does not append a second audit record or webhook. Re-add the same email through the collection with a new create key to reactivate the existing UUID. Current revocation fields are cleared, while paywise retains the append-only revocation audit.

9. Prove the sandbox before re-add

10. Re-add resource-idempotently

Use the same membership fields and a new key. The response must carry the original membership UUID; a second identity is a failure, not a recovery.

11. Verify the exact resource

Runnable membership workflow

Python workflow

Representative response

Failure and recovery

  • On POST timeout or transient 5xx, reuse that logical command’s original key and body within a bounded retry budget.
  • On PATCH timeout, retrieve the exact membership UUID. Do not blindly repeat a stale correction or create a second email variant.
  • 400 email_not_invitable: the address belongs to paywise staff and cannot become a company member; use a different address. email, first_name, and last_name are limited to 128 characters.
  • 409 company_offboarded: the company has left paywise. Every membership command — add, role change, resend, cancel, and revoke — is refused before the body is read; reads stay available. There is nothing to retry.
  • 404 on every membership command after you released the company, or once the company switched to another Partner: the company is outside your access and membership administration is closed. See Release a company.
  • 409 last_company_admin_required requires another current admin; it is not bypassed by role churn. Active memberships cannot use pending-only actions (409 active_membership_immutable); cancelled or revoked memberships answer 409 membership_not_pending to role change, resend, and cancel. Pending, cancelled, and already revoked memberships cannot use the revoke action.
  • A resend 429 honors Retry-After but retains the same resend key. A replayed add or resend returns the membership without setup_url, and so does a repeated add with a new key for an email and role that already exist; if you lost the link, resend once more with a new command key.
  • Adds are budgeted per Partner: 60 membership adds per hour, counting each inline user on a company create, and 30 company creates per hour. The budget is a mail budget: a unit is consumed only when an invitation is actually sent — a replayed command, a refused one, and a repeated add that transparently reuses the existing membership (no new invite) cost nothing. Both buckets are visible in GET /partner/v2/usage/ as mail:membership-create and mail:company-create. A 429 throttled on an add or company create refuses the whole request; wait at least Retry-After seconds and retry with the same command key (see Public limits).