Customer provisioning
Integrate Invitations, Access Codes, external user IDs, and seat allocation with correct capacity and retry behavior.
Your customer application owns authentication. BusinessMode allocates purchased access to the stable external user ID your backend supplies. This guide connects those two responsibilities, from choosing a provisioning path to handling a lost response and checking access.
Start with a Project, a server-side machine API key, and an Organization with a License containing the required Entitlements. If those are not set up, follow the Quickstart. See Projects, Organizations, and Licenses for the object boundaries.
Customer user → your application
- Your applicationAuthentication and sessionStable
externalUserId - Your backendDerives the user IDHolds
bm_sk_... - BusinessModeLicense → AssignmentGrant → Effective Entitlement
1. Establish identity and credentials
Keep the customer account, authentication, session, profile, and email relationship in your application. Customer users do not need to become BusinessMode Supabase Auth users. Your backend derives externalUserId from its authenticated user, rather than trusting an arbitrary user ID submitted by the browser.
BusinessMode identifies that customer user within a Project by (projectId, externalUserId). Choose a stable identifier from your own system. Do not change it whenever an email address changes.
Recipient email is not automatically externalUserId. For a Customer Invitation, email is the delivery address. Your application authenticates the person following the link, applies its recipient/account policy, and supplies that account's stable external ID. Do not blindly use the invitation email as the ID. The invitation token is a bearer credential; possession is not proof of the recipient's identity in your application.
Machine calls use Authorization: Bearer bm_sk_.... Keep the machine API key server-side only: never in browser JavaScript, mobile application binaries, localStorage, or public environment variables. Protect the customer-facing endpoint in your own backend with your normal authentication and request protections. BusinessMode authenticates your integration, not the customer session.
The three API boundaries are different:
| Caller | OpenAPI security scheme | Responsibility |
|---|---|---|
| Project developer | DeveloperBearer | Control-plane management with current direct Project membership. |
| Organization administrator | HumanBearer | Supported portal operations within current Organization authority. |
| Customer backend | MachineApiKey | Runtime assignment, redemption, release, and access checks in the key's Project. |
See Authentication. The HTTP examples below use placeholders, not usable credentials. Append each operation path to this environment's API base: https://api.businessmode.io/v1. Raw HTTP is the public integration baseline; this guide does not require the private repository SDK.
2. Choose a provisioning path
The mechanisms can coexist for the same Organization or License. Organization-administrator delegation is an ownership choice layered over provisioning, not a fourth kind of customer redemption credential.
| Path / best when | Who initiates | Customer interaction | When externalUserId is known | Seat created during | Credential involved |
|---|---|---|---|---|---|
Direct assignment Known user, immediate backend workflow | Your backend | No separate redemption handoff | Before assignment | Assignment command | Server machine API key |
Customer Invitation Email-driven onboarding | Developer or Organization administrator | Open email link and authenticate in your app | At redemption, from your app's identity | Redemption, unless an assignment is reused | Invitation bearer token, plus your backend's machine key |
Access Code
| Developer or Organization administrator | Supply code and authenticate in your app | At redemption, from your app's identity | Redemption, unless an assignment is reused | Access Code bearer credential, plus your backend's machine key |
Organization administrator Customer-managed distribution | Developer delegates; administrator uses portal | Administrator signs into BusinessMode; end user still uses your app | Not needed to create an email Invitation; supplied later by your backend | Chosen provisioning workflow, not administrator onboarding | Human session for portal; Invitation/Access Code and machine key for customer redemption |
- Direct assignment
- Your backend already knows the external user ID.
- Customer Invitation
- Developer / administrator → email link → your customer app.
- Access Code
- Developer / administrator → bearer code → your customer app.
- Customer administrator
- BusinessMode human session → portal → Invitations / Access Codes.
Your app authenticates the customer for each runtime handoff ↓
- Your backendMachine API call with the customer external ID
- AllocationAssignment / Grants / Effective Entitlements
- AuthorizationYour backend checks
hasAccess
For the conceptual differences, read Provisioning.
3. Assign a known user from your backend
Use direct assignment when your backend already knows which authenticated customer user should receive a seat on a particular License. Authorize that business decision in your application, then call Create machine seat assignment.
POST /licenses/{licenseId}/assignments
Authorization: Bearer bm_sk_...
Idempotency-Key: 00000000-0000-4000-8000-000000000001
Content-Type: application/json
{"externalUserId":"customer_user_123"}Replace the License placeholder and generate a fresh UUID for each new logical assignment attempt, not the illustrative UUID above. Store the key with the attempted path and body before sending. If the response is lost, retry the same path, body, and Idempotency-Key. Do not rotate the key for an ambiguous retry. See Idempotency & retries.
The response identifies a canonical Assignment. It is not a permanent authorization decision. A separate new assignment request for an already-seated user is rejected; direct assignment is not the credential-redemption reuse path described below. License validity and valid Grants still determine access. Use hasAccess when gating a capability.
For an operator/developer workflow with many known external IDs, use CSV roster preview before bulk seat assignment. Invalid rows are reported, not silently dropped; fix the roster rather than treating a partial preview as an approved allocation request.
4. Integrate Customer Invitations
An Invitation is an email-driven handoff into your application:
- A developer or Organization administrator creates a Customer Invitation for a License and recipient email.
- BusinessMode records a durable invitation email delivery with its destination snapshot.
- The email sends the recipient to your customer-application destination with
businessmode_invitation=<token>appended as a query parameter. - Your application extracts the token, authenticates its own user, and sends the token securely to your backend.
- Your backend derives the stable
externalUserIdand callsPOST /invitations/redeemwith its machine key. - BusinessMode creates or reuses an Assignment and records the Invitation as redeemed.
Configure the HTTPS invitation destination in the developer product's Project Settings. It must not already contain the reserved businessmode_invitation parameter. The developer creation operation explicitly accepts acceptUrl; the portal creation operation reads the Project configuration on the backend.
Treat the token as a bearer credential. Do not log it, send it to analytics, expose it to unrelated pages, or persist it longer than necessary. Design your callback so the token is handed off securely, then remove it from the visible URL. Avoid loading third-party tracking or leaking the callback URL through referrers. If login is required, retain the pending handoff only as long as needed in your protected application flow, not in an unvalidated redirect URL.
Customer Invitation creation does not reserve a seat. It does not increment assignedCount or reserve either License capacity or Workspace organization-user capacity. An Invitation is not a guarantee that capacity will still be available when the recipient arrives.
POST /invitations/redeem
Authorization: Bearer bm_sk_...
Content-Type: application/json
{
"token": "bminv_...",
"externalUserId": "customer_user_123"
}There is no generic Idempotency-Key requirement on Invitation redemption. For a first redemption, the Invitation must be usable and the License active. If this user already has an active (unreleased) assignment on that License, redemption reuses it without duplicating the seat. Otherwise License and Workspace capacity are checked at redemption when a new allocation is required, through the canonical assignment lifecycle.
Same-user replay returns history, not a new seat
If external user X redeems the same Invitation again, BusinessMode returns the original redemption result: invitationId, assignmentId, licenseId, externalUserId, and redeemedAt. It creates no additional Assignment or redemption event. Another external user cannot consume that already-redeemed Invitation.
If the original seat is later released, same-user Invitation replay still returns the historical original redemption result. It does not create a replacement seat. Treat redemption as consuming this credential for this user, not as an “ensure seat exists” operation. Restore access through an appropriate new provisioning action after checking current state.
Destination changes are not retroactive
An Invitation snapshots its destination at creation. Changing Project Settings from URL A to URL B does not rewrite the existing Invitation or an already-created email delivery. Clearing the setting does not revoke old Invitations; a pending email can still use its original snapshot. New portal Invitations need a configured destination, but existing credentials retain their own lifecycle.
5. Integrate Access Codes
Use an Access Code when your customer workflow can distribute or accept a bearer code rather than an email link:
- A developer or Organization administrator creates a code for a License.
- Copy the full
bmac_...value from the one-time management response and hand it to the intended workflow securely. Later management reads expose onlycodePrefix, not the full code. - Your application accepts the code and authenticates its customer user.
- Your backend sends the code and that user's stable external ID to
POST /access-codes/redeem. - BusinessMode creates or reuses an Assignment, records redemption history, and increments lifetime usage for this user's first successful use of this code.
See Create Access Code and Create portal Access Code. Creating a code does not reserve a seat. Keep the raw code out of analytics and logs, just like an Invitation token.
POST /access-codes/redeem
Authorization: Bearer bm_sk_...
Content-Type: application/json
{
"code": "bmac_...",
"externalUserId": "customer_user_123"
}The Access Code redemption operation has same-user domain replay, not a generic Idempotency-Key requirement.
Lifetime usage is not a seat pool
maxRedemptions limits lifetime successful unique-user uses of this Access Code. A null limit means no code-specific redemption cap; it does not bypass License or Workspace limits.
- First successful use by external user X creates redemption history and increments
redemptionCountby one. - If X already has an active assignment on the target License, the first code redemption reuses that assignment and still consumes one code redemption. Code uses are not a count of new seats created.
- Same-user Access Code replay returns the prior redemption result, including its original Assignment and timestamp. It does not increment
redemptionCountagain. - Releasing a seat does not restore Access Code redemption capacity. The lifetime use remains recorded.
- If that seat is later released, using the same code for X returns the historical redemption; it does not create another seat. This is not a general seat-restoration operation.
For a new user, a revoked, expired, or exhausted code is rejected. A code shown as active can still fail redemption because the License is inactive or a new assignment would exceed License/Workspace capacity. Same-user replay is a lookup of prior successful use, not a fresh promise of current redeemability or access.
6. Delegate distribution, not customer identity
An Organization administrator is a BusinessMode-authenticated human with current Organization-scoped authority. This is separate from a customer external user, an API key, or Project developer authority. A human may hold more than one role, but Organization administration alone does not grant Project-wide configuration access.
The Project developer invites an Organization administrator. The administrator authenticates with BusinessMode and uses the Organization portal for supported operations, including creating Customer Invitations or Access Codes. The administrator does not need the customer's externalUserId to create an email Invitation.
Customer redemption still passes through your application and backend. The administrator's human session does not replace the customer's login, and the customer does not receive the administrator's credentials. Your backend supplies the machine key and the authenticated customer's external ID.
If the Project invitation destination is missing, portal Invitation creation returns missing-configuration guidance; an administrator must contact the application provider rather than edit Project Settings. Access Codes do not depend on that destination setting.
7. Keep three capacity models separate
| Limit | What it counts | What releases capacity? |
|---|---|---|
License seat capacity seatLimit | Current unreleased assignments on one License. | Releasing an assignment frees that License allocation. |
Workspace organization-user capacity orgUserLimit | Distinct Project + Organization + externalUserId allocation identities across the Account. | Releasing the user's final unreleased assignment within that Project/Organization removes that organization-user contribution. |
Access Code lifetime redemption capacity maxRedemptions | Lifetime successful unique-user uses of one code. | Seat release does not replenish it. |
A user allocated two Licenses in the same Organization counts once for Workspace billing capacity, but consumes a seat on each License. The same external user in two Organizations counts twice for Workspace capacity. Naturally expired Licenses can still have unreleased allocations; usage is not a count of users with current access.
An additional seat for an already-allocated organization user does not consume another Workspace organization-user slot, even when the Workspace is over its limit. That License must still be active and have seat capacity. BusinessMode's backend enforces the limits; do not duplicate a preflight capacity check in your app as authorization. See Members & seats and Pricing.
- Is the credential or command usable?Check workflow validity. For a first Access Code use, check lifetime redemption capacity too.
- Is the License active?It must have started, not expired, and not been revoked.
- Does this user already have an unreleased assignment on this License?Yes → reuse where permittedInvitation / Access Code redemption can reuse the seat. A first code use still counts. A new direct assignment request is rejected.No → enforce both limitsLicense seat capacity and Workspace organization-user capacity must allow the new allocation.Then create Assignment → Grants → reconcile effective access.
A failed required check stops this new allocation. A receipt still is not an access decision.
This is a decision model, not a promise of error-check ordering. Invitation validity and code expiry/revocation are workflow-specific. Access Code lifetime capacity is checked separately before a first redemption reaches assignment. Prior same-user redemptions return history before this new-allocation path. Redemption/allocation uses BusinessMode's canonical transactional seat and Grant lifecycle; no cross-service transaction or distributed exactly-once delivery is implied.
What should I choose?
- Known user, immediate backend workflow: direct assignment.
- Email-driven user onboarding: Customer Invitation.
- Redeemable/shared distribution mechanism: Access Code, with an appropriate lifetime limit and expiry.
- Customer-managed distribution: Organization administrator, using the supported portal paths.
Combine these when the workflows differ. Do not select a mechanism because it appears to bypass a capacity limit; new allocations go through canonical enforcement.
8. Handle retries and failures by operation
The request-idempotency column below is generated from the committed OpenAPI extension, not a parallel list of requirements. Follow the linked operation for its exact path, security, body, and responses.
| Operation | Generic Idempotency-Key | Retry behavior |
|---|---|---|
| Direct machine seat assignment | Required | Retain the same path, body, and request key after uncertainty. |
| Customer Invitation creation | Required | Retain the original destination snapshot and request key. |
| Access Code creation | Required | Retain the original selection and request key. |
| Portal Customer Invitation creation | Required | Retain the same portal request and key; current human authority still applies. |
| Portal Access Code creation | Required | Retain the same portal request and key; current human authority still applies. |
| Customer Invitation redemption | Not required | Same-user domain replay returns the original redemption, not a replacement seat. |
| Access Code redemption | Not required | Same-user domain replay returns the prior redemption, not a replacement seat. |
| Machine seat release | Not required | Targets one exact Assignment. Already-revoked is a no-op; never substitute a newer Assignment ID. |
For marked creation operations, retain one key for the same logical request, including the original scope and normalized body. An ambiguous Invitation creation retry must keep its original acceptUrl, even if Project Settings changes in the meantime. The caller's current authorization still applies on replay.
For an ambiguous redemption, deliberately resubmit the same token/code with the same authenticated external user ID. Same-user domain replay can recover the original result without creating another redemption. Never change the user ID just to retry, and do not mistake a recovered historical result for restored access. These calls have no required generic Idempotency-Key.
Handle failures by category:
- Credential or lifecycle: malformed/unknown credentials; Invitation expired, revoked, or already redeemed by another user; Access Code expired, revoked, or exhausted; License inactive. Do not loop a permanent failure. Recheck state or start an appropriate new provisioning workflow.
- Capacity: License seat limit or Workspace organization-user limit. Surface an actionable capacity problem; issuing another Invitation or code does not create capacity.
- Integration: missing/invalid machine authorization, incorrect IDs, or malformed input. Correct the integration rather than exposing credentials or raw request bodies in diagnostics.
- Transport uncertainty: distinguish an unknown result from an explicit rejection. Use retained request idempotency or domain replay as appropriate; avoid automatic unbounded retries.
The API pages document response statuses and error envelopes. Preserve request IDs for support without logging bearer credentials. See Idempotency & retries for the cross-cutting rules.
9. Release allocations; authorize with hasAccess
Release machine seat assignment targets the exact Assignment ID. Releasing it revokes that Assignment and its originating Grants, decrements the License allocation, and reconciles Effective Entitlements. Repeating release of that same already-revoked Assignment is a no-op under current authorization; it is not a request to release whichever seat the user might receive later.
Release does not delete the customer's identity, erase Invitation or Access Code redemption history, replenish Access Code capacity, or guarantee that all access disappears. Another valid Grant may preserve the same Entitlement. Revoked assignments, redeemed Invitations, code redemptions, and Audit events remain operational history.
Provisioning history is not current authorization state. Finish the workflow by querying Check effective entitlement access when your application needs to authorize a capability:
GET /access?externalUserId=customer_user_123&entitlementKey=analytics
Authorization: Bearer bm_sk_...Read hasAccess from the response. URL-encode real query values, derive the user ID on your backend, and gate the requested capability on that current decision—not on a stored Invitation receipt, code redemption, or seat response. Access decisions explains how valid Grants produce the Effective Entitlement projection.