Organizations
Your app's own multi-tenancy. Every user belongs to an organization, sees only its own, and you see all of them.
Organizations are how your product separates one customer from another. They live in your app's
own database (its orgs table), and they have nothing to do with your Authoric organization, the
account you sign in to the console with. When someone signs up to your app they get an
organization of their own; the people they invite join it; and the rows they create belong to it.
This page is the model. The pages under it cover members and invitations, requiring two-factor, and the org reach of the agents, jobs and keys that act for you.
Two kinds of organization
| Your Authoric organization | Your app's organizations | |
|---|---|---|
| Where it lives | The platform | Your app's database |
| Who is in it | You and your teammates, in the console | Your app's users |
| What it owns | Your apps, your subscription | Rows in org-scoped tables, files, agents and jobs made inside the app |
| How it is addressed | Its uid | Its uid (an orgUid, or org_uid on a row) |
The two sets are separate number spaces, and nothing in one is ever compared to or stored in the other. An app's caller can never name another app, or the platform, whatever its organization.
Who sees what
One rule, applied on every surface: REST, handlers, realtime, storage, agents and jobs.
- Inside the app, a caller sees only its own organization. A signed-in user acts in their
session's active organization. Another
organization's row, file, job or agent answers
404, exactly like one that does not exist, so a response never confirms that someone else's record is there. - You, the app's owner, in the console, see everything in the app: every organization's rows and records, each labeled with the organization it belongs to.
- What you create from outside the app is yours (it belongs to no app organization) unless you name one. Creating a row in an org-scoped table from the console means choosing which organization it belongs to.
- What acts for you without you clicking (your agents, the Arbiter, your scheduled jobs and API keys) reaches only the organizations its org reach allows. The default is none.
Policies still decide what a caller may do; the organization decides where. A caller needs both. See Access control.
Org-scoped and shared tables
When you create a table, Scope by org is on by default. Such a table gets an org_id column
that Authoric fills in and enforces:
- a row created inside the app belongs to the caller's active organization, automatically;
- reads, updates and deletes inside the app only ever see that organization's rows;
- the column itself never appears in a request or response. A caller on your side (your console,
or a job acting across organizations) sees it as
org_uid.
Turn Scope by org off for reference data every organization should see, such as a product catalog or a list of countries. A shared table's rows are visible to every caller whose policies allow the read.
Do not filter by organization yourself
Inside the app, scoping is already applied before your filter, your handler or your policy runs.
Sending org_id (or any other scope column) in a body is a 400 naming the field, not a way to
pick an organization.
How organizations come to exist
- Sign-up.
POST /{appUid}/auth/signupcreates the user and a new organization, and makes the user its owner. Sign-up can never join an existing organization (SIGNUP_ORG_NOT_ALLOWED). - Creating a member directly. An organization's administrator can create an account for someone
(
internal-create); that account gets its own organization too, and joins the inviting one at once. - A user creating another. When your app allows it (App settings › Sign-in, "Users can
create more organizations"), a user creates more organizations with
POST /{appUid}/autocrud/orgs/actions/createand{ "name" }, and owns each one, up to the number per user you set (ORG_LIMIT_REACHEDpast it). It is off by default (ORG_CREATION_DISABLED), and needs a confirmed email address (EMAIL_NOT_VERIFIED).
Every user therefore has a home organization, the one created with their account. They cannot leave it or be removed from it. They can belong to any number of others through invitations.
Belonging to several organizations
A session acts in one organization at a time. To list the ones a user can switch to:
curl "$AUTHORIC_URL/$APP_UID/autocrud/org_memberships/actions/my-orgs" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"items": [
{
"membershipUid": "0199a3c2-5b1f-7a10-9d2e-6c4b8f0e1a22",
"status": "accepted",
"isActive": true,
"acceptedAt": "2026-10-02T14:11:09.000Z",
"org": { "uid": "0199a3c2-5b1d-74e0-8c55-1f0e2a9b7d31", "name": "Dana Reyes", "slug": "dana-reyes" }
},
{
"membershipUid": "0199a5f1-2c07-7b3d-a1e9-0d5f7c2b9e64",
"status": "accepted",
"isActive": false,
"acceptedAt": "2026-10-11T09:40:52.000Z",
"org": { "uid": "0199a5f0-9e33-7d42-b6a8-2f1c0e7d5b19", "name": "Northwind Clinics", "slug": "northwind-clinics" }
}
],
"paging": { "limit": 50, "offset": 0, "count": 2, "total": 2 },
"actor": { "subjectType": "user", "email": "dana@example.com", "activeOrgUid": "0199a3c2-5b1d-74e0-8c55-1f0e2a9b7d31" }
}Accepted memberships are listed by default; pass ?status=pending|revoked|expired|any to widen it.
Switch by refreshing the session with the organization's uid as activeOrgUid
(Sessions). The server checks the membership and the
organization's two-factor requirement before issuing anything.
Renaming an organization
POST /{appUid}/autocrud/orgs/actions/rename-active with { "name": "Northwind Clinics" } renames
the caller's active organization (2 to 120 characters). It never takes an organization id or
uid: the target is always the session's own.
Errors you will meet here
| Status | Code | When |
|---|---|---|
| 404 | (none) | A row, file or record belongs to another organization. Identical to "does not exist". |
| 400 | ROW_ORG_REQUIRED | You created a row in an org-scoped table from outside the app without naming its organization. |
| 400 | ROW_ORG_NOT_FOUND | The organization named for a row is not one of this app's, or not one the caller reaches. |
| 403 | CROSS_PLANE_FORBIDDEN | An app caller tried to reach another app or the platform. |
| 400 | SIGNUP_ORG_NOT_ALLOWED | Sign-up tried to name an existing organization. |
| 400 | ORG_NAME_INVALID | A rename outside 2 to 120 characters. |