Invite sellers

Invite by email with their payout mode and your commission. member.joined tells you they're in.

A seller joins by accepting an invitation with their own QBitFlow account (or a new one). Until they accept, they aren't a seller.

  1. Your server → QBitFlow API: Invites them by email
  2. QBitFlow API → Your server: 201: the invitation and its link
  3. QBitFlow API → The seller: Emails the link
  4. Your server → The seller: Or you share it (sendEmail: false)
  5. The seller → QBitFlow API: Accepts, with their own account
  6. QBitFlow API → Your server: member.joined, with their userUuid

Invite a seller

Invite a seller

members-invite.sh

# An organization key (an admin's), in the key's mode. holdFunds holds their payments until you# trust them; organizationFeePercent is your commission. The link is also emailed ("sendEmail":# false to share it yourself): they are a member once they accepted (the member.joined webhook).curl https://api.qbitflow.app/v2/invitations \  -H "X-API-Key: $QBITFLOW_API_KEY" \  -H "Content-Type: application/json" \  -H "Idempotency-Key: $(uuidgen)" \  -d '{    "email": "seller@example.com",    "role": "user",    "holdFunds": true,    "organizationFeePercent": "10",    "redirectUrl": "https://shop.example.com/sellers/welcome"  }'# 201: a CreatedInvitation: the invitation's fields and "link", the link to accept
const created = await client.invitations.create({	email: 'seller@example.com',	holdFunds: true, // hold their payments until you trust them (always sent)	organizationFeePercent: '10', // your commission on their payments: '0' to '50' %	redirectUrl: 'https://shop.example.com/sellers/welcome',});// QBitFlow also emails them a link of their own. They are a member once they accepted (the// member.joined webhook).console.log(`invitation ${created.uuid}: ${created.link}`);
created = client.invitations.create(    email="seller@example.com",    hold_funds=True,  # hold their payments until you trust them (always sent)    organization_fee_percent="10",  # your commission on their payments: "0" to "50" %    redirect_url="https://shop.example.com/sellers/welcome",)# The seller exists once they accept: wait for the member.joined webhook.# created.uuid is what member.joined's invitation_uuid names; its user_uuid is the seller.# QBitFlow also emails them a link of their own.print("invitation", created.uuid, "link:", created.link)
created, err := client.Invitations.Create(ctx, &qbitflow.CreateInvitationParams{	Email:                  "seller@example.com",	HoldFunds:              true, // hold their payments until Members.Trust (always sent)	OrganizationFeePercent: "10", // your commission on their payments: "0" to "50" %	RedirectURL:            "https://shop.example.com/sellers/welcome",})if err != nil {	log.Fatal(err) // 409 already_joined for a member}// QBitFlow also emails the seller a link of their own. The seller exists once they// accepted: wait for member.joined.fmt.Println("invitation", created.UUID, "link:", created.Link)
$created = $client->invitations->create(new \QBitFlow\Params\CreateInvitationParams(	email: 'seller@example.com',	holdFunds: true,              // hold their payments until you trust them (always sent)	organizationFeePercent: '10', // your commission on their payments: "0" to "50" %	redirectUrl: 'https://shop.example.com/sellers/welcome',));// QBitFlow also emails them a link of their own.echo "Invitation {$created->uuid}: {$created->link}\n";
  • email: QBitFlow emails the link there; the answer carries it too, for your own onboarding.
  • sendEmail (default true): false sends no invitation email; share the link yourself (your own page or email). Signing up through it still sends the seller QBitFlow's confirmation email. Test sellers accept from a real inbox too: invite addresses you control (you+seller1@yourdomain.com) once, and keep them (a key can't create accepted test sellers yet).
  • holdFunds (required): true holds their sales, false pays them directly (the previous step).
  • organizationFeePercent: your commission, 0 to 50.
  • redirectUrl: where they land once they've accepted.

It answers 201 with the pending invitation and its link, valid for 7 days. A new invitation of the same person replaces the open one; a current seller answers 409 already_joined. At most 50 invitations an hour, both modes together.

Watch out

Sellers already on QBitFlow

An account can belong to several organizations: a seller already on QBitFlow accepts with their existing account, and keeps their other organizations. Only the invited address's account can accept (403 invitation_email_mismatch): invite another address they own.

Know when they join

When they accept, member.joined reaches your endpoint (step 1):

member.joined· the body your endpoint receives

{  "createdAt": "2026-10-01T12:00:00Z",  "data": {    "invitationUuid": "01a0f755-f200-7000-8000-00000000000a",    "organizationFeePercent": "5",    "userUuid": "019eca82-5680-7b00-8000-0000000000b1",    … 7 more fields  },  "id": "evt_634bf7f1-010f-5f60-8232-06c30f013f28",  "test": false,  "type": "member.joined",  "version": "v2"}
The contract's example (webhooks.md § Examples)
  1. data.invitationUuid: Matches the event to your invitation.
  2. data.organizationFeePercent: Your commission, as you invited them.
  3. data.userUuid: Store it: it's how you act for them (On-Behalf-Of).

Avoid

Trust the invitationUuid in your redirect URL

Anyone can open that URL.

Do

Wait for member.joined

Signed, and it carries their userUuid.

Keep track

Open invitations, and revoking one (its link stops working; an accepted one answers 409):

List invitations

invitations-list.sh

# The pending invitations.curl "https://api.qbitflow.app/v2/invitations?status=pending" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a page of Invitation ({ items, hasMore, nextCursor? })
const page = await client.invitations.list({ status: InvitationStatus.Pending });for (const invitation of page.items) {	console.log(`${invitation.uuid} ${invitation.email}, expires ${invitation.expiresAt}`);}
page = client.invitations.list(status=qbitflow.InvitationStatus.PENDING)for invitation in page.items:    print(invitation.uuid, invitation.email, "expires", invitation.expires_at)
invitations, err := client.Invitations.List(ctx, &qbitflow.InvitationListParams{	Status: qbitflow.InvitationStatusPending,})if err != nil {	log.Fatal(err)}for _, inv := range invitations.Items {	fmt.Println(inv.UUID, inv.Email, "expires", inv.ExpiresAt.Format(time.RFC3339))}
$page = $client->invitations->list(new \QBitFlow\Params\InvitationListParams(status: \QBitFlow\Enums\InvitationStatus::PENDING));foreach ($page->items as $invitation) {	echo "{$invitation->uuid} {$invitation->email}, expires {$invitation->expiresAt->format('Y-m-d')}\n";}
Revoke an invitation

invitations-revoke.sh

# Revokes an invitation not accepted yet: its link stops working.curl -X DELETE "https://api.qbitflow.app/v2/invitations/$INVITATION_UUID" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: an Invitation
// Only before it is accepted: its link stops working (204, nothing returned).await client.invitations.revoke(invitationUuid);console.log(`revoked ${invitationUuid}`);
client.invitations.revoke(invitation_uuid)  # 204: its link stops workingprint("revoked", invitation_uuid)
// 204: its link stops working.if err := client.Invitations.Revoke(ctx, invitationUUID); err != nil {	log.Fatal(err) // 409 once accepted or revoked}fmt.Println("revoked", invitationUUID)
$client->invitations->revoke($invitationUuid); // a pending invitation only (204)echo "Revoked {$invitationUuid}: its link no longer works\n";

Your sellers, a page at a time, or one by userUuid: their fee, trustedAt (absent while you hold their funds) and the currencies they accept.

List members

members-list.sh

# One page of the organization's members (an admin key).curl "https://api.qbitflow.app/v2/members?limit=20" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a page of Member ({ items, hasMore, nextCursor? })
const page = await client.members.list({ limit: 20 });for (const member of page.items) {	console.log(`${member.email}: fee ${member.organizationFeePercent} %`);}
page = client.members.list()for member in page.items:    print(member.user_uuid, member.email, f"fee {member.organization_fee_percent}%")
page, err := client.Members.List(ctx, &qbitflow.MemberListParams{Limit: 20})if err != nil {	log.Fatal(err)}for _, m := range page.Items {	fmt.Printf("%s %s %s, fee %s %%, trusted: %v\n", m.UserUUID, m.FirstName, m.Email, m.OrganizationFeePercent, m.TrustedAt != nil)}
foreach ($client->members->iterate() as $member) {	printf("%s %s %s: fee %s %%, %s\n", $member->userUuid, $member->firstName, $member->email,		$member->organizationFeePercent, $member->trustedAt !== null ? 'trusted' : 'funds held');}
Get a member

members-get.sh

# One member, by their user uuid.curl "https://api.qbitflow.app/v2/members/$MEMBER_UUID" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a Member
const member = await client.members.get(memberUuid);console.log(`${member.firstName} ${member.lastName ?? ''} <${member.email}>`);console.log(`fee ${member.organizationFeePercent} %, trusted: ${member.trustedAt ?? 'no'}`);for (const currency of member.acceptedCurrencies) console.log(`  accepts ${currency.code}`);
member = client.members.get(member_uuid)  # their user uuidprint(member.first_name, member.last_name, "trusted:", member.trusted_at is not None)for currency in member.accepted_currencies:  # empty: their checkouts answer 409    print("  accepts", currency.code)
member, err := client.Members.Get(ctx, memberUUID) // their userUuidif err != nil {	log.Fatal(err)}fmt.Printf("%s %s <%s>, fee %s %%, trusted: %v\n",	member.FirstName, member.LastName, member.Email, member.OrganizationFeePercent, member.TrustedAt != nil)for _, c := range member.AcceptedCurrencies {	fmt.Println("  accepts", c.Code)}
$member = $client->members->get($memberUuid); // the member's userUuidecho "{$member->firstName} " . ($member->lastName ?? '') . " <{$member->email}>, joined {$member->joinedAt->format('Y-m-d')}\n";foreach ($member->acceptedCurrencies as $currency) {	echo "  accepts {$currency->code}\n"; // none: their checkouts answer 409 merchant_not_ready}

Check it

Invite one of your addresses: it's pending in Dashboard/Members/Invitations. Accept it: member.joined arrives, and the seller is listed by your code and in Dashboard/Members.