Documentation Index
Fetch the complete documentation index at: https://docs.upsolve.ai/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The project user model replaces the legacy/register-tenant flow. It separates three concerns that /register-tenant collapsed into a single call:
- Project organizations — the customer entity that a group of users belongs to (a company, a team, a workspace in your product). Created once per organization with
/v1/api/projects/register-organization. - Project users — an individual user inside an organization. Created once per user with
/v1/api/projects/register-user. - JWT tokens — issued per session via
/v1/api/projects/user-tokenusing a user’s ID.
Legacy
/register-tenant still works and remains supported. New integrations
should use this flow. Existing integrations should migrate when they need
multiple users per organization, shared customizations, or external-id-based
identification.Data Model
- One project organization can contain many users.
- One project user belongs to exactly one project organization.
- One role is assigned to each user and defines their permissions.
- One space is automatically created per (application, project organization) — that’s where users see and customize dashboards.
End-to-End Flow
1. Register an Organization (once per customer entity)
externalId — what it is and why you want it
externalId is any opaque string you control — typically the identifier your application already uses to reference the organization (e.g. a company UUID). Upsolve stores it alongside the Upsolve-issued projectOrganizationId and uses it to resolve subsequent calls.
Why this matters: wherever an Upsolve endpoint accepts projectOrganizationId, it now accepts either the Upsolve UUID or the externalId. The resolver tries the UUID lookup first and falls back to externalId within the same organization + project scope. So once an externalId is set, your application never needs to persist Upsolve’s UUID — it can keep using the identifier it already has.
Use it when:
- You already have a stable identifier for your organizations and don’t want to store a second one.
- You want
/register-usercalls to read naturally:projectOrganizationId: "acme-internal-uuid-1234".
externalId is optional) if you’re happy to persist Upsolve’s UUID.
Side effects of /register-organization
- A row is inserted into
project_organizations. - For every existing application in the project, a space is created. Each space is automatically populated with the application’s published dashboard templates (lazy fork — templates render directly and only persist once a user customizes them).
- A
space_initialization_jobsrow is created per space for traceability.
2. Register a User (once per user account)
Notes
projectOrganizationIdresolves polymorphically — pass either the Upsolve UUID or theexternalIdyou set in step 1.userIdis optional. If you supply your own UUID, that becomes the user’s permanent identifier in Upsolve — you can re-use it on every future/user-tokencall without persisting Upsolve’s user UUID. If you omit it, Upsolve generates one and returns it.userRoleIdmust refer to a project user role that exists in the same project. Roles define permissions (addChart,editCharts, etc.) and are reusable across users.propertiesare arbitrary JSON. They’re surfaced in row-level security (RLS) rules as{{user.properties.X}}placeholders. See Tenant Properties.
Common errors
| Status | Cause |
|---|---|
400 Bad Request | Missing required field (name, projectId, userRoleId, projectOrganizationId). |
404 Not Found | projectOrganizationId doesn’t match any row’s id or external_id in your scope. |
404 Not Found | userRoleId doesn’t belong to the given project. |
409 Conflict | A user with the same userId or the same (project, name) already exists. |
3. Fetch a User Token (on every login / dashboard load)
<UpsolveDashboard token={...} />. Tokens expire after the requested duration (default 1h). Fetch a new token before expiry — changing the token causes the dashboard to refresh with the new user’s permissions.
/user-token does not require the project ID or organization ID. The JWT
payload is derived from the project_users row identified by userId — so
the token automatically carries the user’s project, organization,
permissions, and properties.Standard Login Loop
The simplest stable backend integration is:- On user sign-up: call
/register-organization(if needed) and/register-user. Store nothing about Upsolve — just remember that the user is registered. - On every page load that mounts an Upsolve dashboard: call
/user-tokenwith the user’s ID. Pass the returned token to the frontend. - If
/user-tokenreturns404 User not found: the user isn’t registered yet — call/register-userthen retry.
Security
- API key calls must go through your backend — never expose API keys to the browser.
- Tokens are scoped to a single project user. They cannot read other users’ data, even within the same project organization, unless your role grants it.
- Always use HTTPS.
How this differs from /register-tenant
| Concern | /register-tenant (legacy) | /register-user flow |
|---|---|---|
| Users per organization | 1 (each call creates a new admin user inside a new tenant) | Many (one organization, many users) |
| Returns a JWT directly | Yes | No — call /user-token separately |
| Customer-provided identifiers | No — Upsolve assigns | Yes — pass externalId (org) and userId (user) of your choosing |
| Shared spaces / customizations | No — each tenant has its own spaces | Yes — all users in one organization share the same spaces |
| Role re-use | No — permissions live on the tenant | Yes — one role per (organization, permission set), assigned to N users |