Support/Developer Platform/How customer organizations can signup from your website

Developer Platform

How customer organizations can signup from your website

Connect your SaaS marketing-site signup and login to one governed BuildWithHQ customer organization and its first owner.

Signup limit
5 DataRoles + 5 Locations
App secret in browser
Never
Result
One routed AppAccount

Your SaaS marketing site can offer its own branded signup and login while BuildWithHQ creates and secures the customer organization behind it. The customer sees your website and identity provider. Your trusted backend makes the app-bound BuildWithHQ calls.

What one signup creates

  • One top-level customer organization (AppAccount) inside the selected SaaS app.
  • The first active tenant user as the protected account owner.
  • The account-local owner User Type and Account Owner DataRole.
  • Up to five additional initial DataRoles and up to five initial Locations.
  • Stable external organization/user identity links for idempotent retries and later login.

The organization is immediately visible under SaaS Apps > your app > Organizations in the Builder administration area. The owner sees the same organization in the SaaS Account administration workspace. Both read the same routed AppAccount; no import or synchronization job is involved.

1. Create the server credential

Create a live-app API client for this SaaS app with tenant-accounts.write and tenant-users.tokens.issue. Add tenant-accounts.read only when the backend must resolve the organization separately. Keep the resulting bwk_live_ value in a server secret store.

Important

Never put a bwk_live_ credential in HTML, React/Vite public environment variables, browser storage, a mobile bundle, page JSON, logs, or source control. The public form posts to your server, not directly to BuildWithHQ.

2. Authenticate and verify the public signup

Use your identity provider for email verification, password/passkey handling, MFA, recovery, bot defense, rate limiting, signup consent, and session cookies. BuildWithHQ accepts the provider's stable organization ID and stable user subject; it does not accept or store the customer's raw password on this route.

Marketing signup form
    | your HTTPS session + CSRF + bot/rate controls
    v
Your trusted signup BFF
    | verifies email and identity-provider claims
    | uses server-only bwk_live_ credential
    v
POST /v1/apps/{saasAppId}/accounts
    | atomic routed transaction
    v
Organization + owner + initial roles/locations

3. Post the verified signup

import { BuildWithHQConnector } from "@your-server/buildwithhq-connector";

const api = new BuildWithHQConnector({
  saasAppId: process.env.BWHQ_SAAS_APP_ID!,
  credential: process.env.BWHQ_API_CREDENTIAL!,
});

const account = await api.createTenantAccount({
  providerKey: "marketing-auth",
  externalAccountId: verifiedOrganization.id,
  accountName: verifiedOrganization.name,
  primaryEmail: verifiedOwner.email,
  ownerExternalSubject: verifiedOwner.subject,
  ownerEmail: verifiedOwner.email,
  ownerDisplayName: verifiedOwner.displayName,
  dataRoles: [
    { key: "member", name: "Member", canRead: true, canReadRecords: true,
       permissionKeys: ["records.read"] },
    { key: "manager", name: "Manager", canRead: true, canCreate: true,
       canEdit: true, canReadRecords: true, permissionKeys: ["records.read"] },
  ],
  locations: [
    { key: "head-office", name: "Head office", timeZone: "America/Los_Angeles" },
  ],
});

Each role/location key is a caller-defined key used only to correlate the response. The response returns its new dataRoleId or locationId. Send no more than five entries in either array. Add or edit additional roles and locations later through Builder, in-SaaS administration, or the app-scoped Developer API.

4. Handle retries safely

The operation is idempotent for providerKey + externalAccountId. The first creation and a matching replay both return HTTP 201; inspect created (true for creation and false for replay). A different owner subject for that external organization returns HTTP 409 with the shared safe error envelope. Do not generate a new external organization ID for every retry.

5. Log the owner in

After your identity provider authenticates the person, your BFF exchanges the verified external organization ID and user subject for a two-to-fifteen-minute delegated token:

const delegated = await api.exchangeDelegatedUserToken({
  providerKey: "marketing-auth",
  externalAccountId: verifiedOrganization.id,
  externalSubject: verifiedOwner.subject,
  scopes: ["modules.contacts.read", "modules.work-orders.read"],
  lifetimeMinutes: 10,
});

// Return only delegated.accessToken to this authenticated browser session.

The browser uses that bwu_live_ token for the allowed runtime/module routes. It cannot create organizations, manage users, or mint another token. Each call rechecks the active user, DataRoles, Locations, permissions, and record visibility.

6. Verify both administration views

  1. In Builder administration, open the SaaS app and select Organizations. Confirm the organization, owner, user count, DataRole count, Location count, and status.
  2. Log in as the new owner and open Account administration inside the SaaS. Confirm People, Access, Security, Audit, Exports, Developer, and permitted presentation sections.
  3. Add another user with a distinct verified external subject and assign only IDs returned for this AppAccount.
  4. Test duplicate signup, a conflicting owner subject, six roles, six locations, a foreign app ID, missing scopes, and a deactivated user. Each must fail or replay exactly as documented without partial rows.

7. Manage the organization after signup

The trusted backend can search its organizations, update identity and status, maintain DataRoles and Locations, administer invitations, and transfer ownership. Every mutation is app-bound and revision-fenced.

const organizations = await api.listTenantAccounts({ search: "Acme", pageSize: 25 });
const org = await api.getTenantAccountAdmin(organizations.organizations[0].appAccountId);
await api.updateTenantAccount(org.appAccountId, {
  expectedUpdatedUtc: org.updatedUtc,
  accountName: "Acme Field Service",
  primaryEmail: "[email protected]",
  status: "Active",
});
const invitation = await api.createTenantInvitation(org.appAccountId, {
  email: "[email protected]", displayName: "New User", userTypeKey: "member",
  dataRoleIds: [org.dataRoles[0].dataRoleId], locationScopeMode: "all", locationIds: [],
});
const acceptUrl = `https://customer.example/accept-invitation#token=${encodeURIComponent(invitation.acceptanceToken)}`;

Deliver the once-only token from your backend and never log it. Resend rotates it; revoke invalidates it. The recipient supplies a password only to the hosted tenant authentication route. Ownership transfer verifies an active, independently sign-in-capable target and revokes the previous owner's sessions.

Credential boundary

CallerCredentialAllowed work
Public browserYour session; later a short-lived bwu_live_Submit signup to your BFF and use delegated module/runtime routes.
Signup/login BFFServer-only bwk_live_Create/reconcile organizations and exchange verified external users.
Builder ConsoleSigned-in BuildWithHQ Builder sessionList and administer every organization belonging to the owned SaaS app.
Note

The API credential has no database password, table access, or DDL rights. The gateway binds it to one SaaS app and the database role can execute only allow-listed procedures. Client-supplied IDs select targets; they never establish authority.

For the full contract and copyable server starter, download Developer Kit 1.20.3 and read TENANT-PROVISIONING-AND-AUTHENTICATION.md plus HEADLESS-USER-DELEGATION.md.

Capability review: 2026-09-14. For exact current technical availability, use the generated API Map and first-class module inventory.