go-auth
Guides

Organizations

Multi-tenant orgs — creating and managing them, membership and roles, active-org scoping, and invites. curl, Go, and a browser client.

Organizations

Multi-tenancy: users can belong to more than one organization, hold a different role in each, and scope a session to one of them at a time. Disabled by default — with WithOrganizations unset (or Enable: false), none of the routes on this page are mounted at all, and h.services.Org is nil.

For state-changing requests, every curl example below needs the same -H "Origin: https://myapp.com" treatment as Authentication — omitted from most snippets below for brevity, but required in practice.

Configuration

goauth.WithOrganizations(goauth.OrganizationConfig{
    Enable:         true,               // optional, default false
    MaxOrgsPerUser: 10,                 // optional, default 0 (built-in cap of 100)
    InviteTTL:      7 * 24 * time.Hour, // optional, default 7d
})
FieldTypeRequiredDefaultNotes
EnableboolOptionalfalseNothing on this page exists until this is true.
MaxOrgsPerUserintOptional0 — built-in cap of 100Counts orgs a user owns, not every org they're a member of. Enforced on both org creation and being granted/invited as Owner.
InviteTTLtime.DurationOptional7dHow long a code from Creating an invite stays valid.

Expected errors

  • organizations.max_orgs_per_user must be between 0 and 100 (0 = default 100)
  • organizations.invite_ttl must be positive — hint: only reachable via an explicit negative value.

Full reference: Configuration → WithOrganizations.

Frontend client setup

The Client examples below call the same apiRequest helper defined on Client → Setup.

Roles

Roles are compared by weight, not by name. RequireOrgRole(min) checks role.Weight() >= min.Weight(), so “Admin or above” means weight 2 or higher.

RoleWeightCan
owner3Everything, including deleting the org and granting/revoking Owner
admin2Manage members and invites, update org name/slug — everything except delete the org or touch Owner status
member1Read the org, leave it

Granting or revoking Owner requires being an Owner yourself

This is checked separately from the route-level RequireOrgRole, inside the service layer: Change a member's role and Creating an invite both special-case the owner role — an Admin can promote a Member to Admin (that's normal RequireOrgRole(admin) territory), but cannot self-escalate to Owner or hand Owner to anyone else. Only an existing Owner can create or remove another Owner. Attempting it as anything less returns org_forbidden, distinct from the route-level 403 you'd get for not being an Admin at all.

Every org-scoped route below runs through RequireOrgMember first (reads {orgID} from the path, 404 org_member_not_found if you're not a member — indistinguishable from the org not existing, on purpose), then RequireOrgRole if the action needs more than plain membership.

Creating and reading orgs

Create an org — POST /auth/orgs

Auth: any authenticated user. Body: { "name", "slug" }. The caller becomes the org's first member, with role owner.

{
  "id": "a1b2...",
  "name": "Acme Inc",
  "slug": "acme",
  "createdBy": "b3f1...",
  "ownerCount": 1,
  "memberCount": 1,
  "metadata": {},
  "createdAt": "2026-08-09T12:00:00Z",
  "updatedAt": "2026-08-09T12:00:00Z"
}
CodeStatusCause
invalid_name400name is empty
invalid_slug400slug is over 255 bytes
org_slug_reserved400slug is one of the reserved words (api, admin, auth, www, ... — see Configuration for the full list)
org_slug_exists409Another org already has that slug
org_limit_reached400Caller already owns MaxOrgsPerUser orgs

curl

curl -X POST https://api.myapp.com/auth/orgs \
  -H "Content-Type: application/json" \
  -H "Origin: https://myapp.com" \
  -b cookies.txt -c cookies.txt \
  -d '{"name":"Acme Inc","slug":"acme"}'

Programmatic (Go)

org, err := auth.Services.Org.CreateOrg(ctx, goauth.CreateOrgInput{
    Name:    "Acme Inc",
    Slug:    "acme",
    OwnerID: userID,
})

Client

See Create organization for the wrapper and form component.

List your orgs — GET /auth/orgs

Auth: any authenticated user. Every org the caller belongs to, any role. Query params, all optional:

ParamValuesDefaultNotes
roleowner, admin, member— (no filter)Narrows to memberships at that role — "the orgs I own". Anything else is a 400, not a silent fallback; see the callout below.
searchany string— (no filter)Substring match on org name.
orderByname, created_at, member_countnameUnrecognized values silently fall back to the default rather than erroring.
orderDirectionasc, descascSame fallback behavior.
offsetinteger0
limitinteger20Omit entirely for the default page of 20. limit=0 explicitly means unlimited — every org the caller belongs to, in one response. Any other value is capped at 100. This distinction only exists because the param was left off vs. sent as 0 — see the callout below.

An unrecognized role is a 400 — an unrecognized orderBy isn't

The two look like the same kind of param and are deliberately handled differently, here and on every org listing on this page (members, invites, and the admin equivalents).

A bad orderBy falls back to the default because a wrong sort order is cosmetic and immediately visible on screen. A bad role is rejected because a filter that silently widens returns rows the caller explicitly asked to exclude — ?role=Owner (capital O, straight out of a dropdown label) would hand back every member, and a console rendering them under the heading it filtered by would be confidently wrong with no error anywhere. An empty role= still means "no filter", so a <select> bound directly to the query param works unchanged.

The service layer already treats a role this way on every mutation input; as of v0.2.2 the filters agree with it.

Why limit=0 means something different from omitting it

ListUserOrgsInput.Limit is *int, not int, specifically so the service can tell "the caller didn't say" (nil → default 20) apart from "the caller explicitly wants everything" (&0 → unlimited). A plain int can't make that distinction — Go's zero value is 0 either way. The HTTP handler mirrors this by checking whether the limit query param is present, not just parsing it: an absent or unparsable limit becomes nil, limit=0 becomes a pointer to 0. The same semantics apply to members and invites below — but not to the admin GET /admin/users listing, which has no unlimited escape hatch.

{
  "orgs": [
    {
      "id": "b3b12...",
      "name": "Acme Inc",
      "slug": "acme",
      "memberCount": 5,
      "createdAt": "2026-01-15T10:30:00Z"
    }
  ],
  "limit": 20,
  "offset": 0
}

The list response carries no total. Fetch it from GET /auth/orgs/count — the count of orgs matching search (ignoring limit/offset), which is what you paginate against, not orgs.length. Splitting it out keeps List from running a COUNT(*) on every page.


curl

curl "https://api.myapp.com/auth/orgs?search=acme&orderBy=created_at&orderDirection=desc&limit=20&offset=0" \
  -b cookies.txt -c cookies.txt

curl "https://api.myapp.com/auth/orgs/count?search=acme" -b cookies.txt -c cookies.txt
# → { "count": 1 }

Programmatic (Go)

result, err := auth.Services.Org.ListUserOrgs(ctx, goauth.ListUserOrgsInput{
    UserID: userID, Offset: 0, Limit: nil, // nil = default 20; &0 = unlimited
    OrderBy: "created_at", OrderDirection: "desc",
})
// result.Orgs, result.Limit, result.Offset

n, err := auth.Services.Org.CountUserOrgs(ctx, goauth.ListUserOrgsInput{
    UserID: userID, Search: &search,
})

Role is a *domain.OrgRole — leave it nil for every role, or point it at one to narrow the list to memberships at that role:

role := domain.OrgRoleOwner
owned, err := auth.Services.Org.ListUserOrgs(ctx, goauth.ListUserOrgsInput{
    UserID: userID, Role: &role,
})

Client

See List organizations for the wrapper and data table.

Get one org — GET /auth/orgs/{orgID}

Auth: org member. Same response shape as create.

CodeStatusCause
org_not_found404Doesn't exist
org_member_not_found404Exists, but the caller isn't a member — same status either way, so membership can't be probed

curl

curl https://api.myapp.com/auth/orgs/a1b2... -b cookies.txt -c cookies.txt

Programmatic (Go)

org, err := auth.Services.Org.GetByID(ctx, goauth.GetOrgInput{OrgID: orgID, ActorID: callerUserID})

Client

See Get organization for the wrapper.

Updating and deleting orgs

Update an org — PUT /auth/orgs/{orgID}

Auth: org admin or above. Body: { "name"?, "slug"? }. Both fields are pointers, so omitting one leaves it unchanged rather than clearing it. Unlike WithRegistration, this is a partial update.

```json
{
  "id": "b3b12...",
  "name": "Acme Incorporated",
  "slug": "acme",
  "memberCount": 5,
  "createdAt": "2026-01-15T10:30:00Z"
}

| Code | Status | Cause |
|---|---|---|
| `org_not_found` | 404 | Doesn't exist |
| `org_slug_reserved` | 400 | New `slug` is a reserved word |
| `org_slug_exists` | 409 | New `slug` is already taken by another org |

---

#### curl

```bash
curl -X PUT https://api.myapp.com/auth/orgs/a1b2... \
  -H "Content-Type: application/json" \
  -H "Origin: https://myapp.com" \
  -b cookies.txt -c cookies.txt \
  -d '{"name":"Acme Incorporated"}'

Programmatic (Go)

newName := "Acme Incorporated"
org, err := auth.Services.Org.UpdateOrg(ctx, goauth.UpdateOrgInput{
    OrgID: orgID,
    Name:  &newName, // nil Slug leaves the slug unchanged
})

Client

See Update organization for the wrapper and form component.

Delete an org — DELETE /auth/orgs/{orgID}

Auth: org owner. Deletes the org outright — no soft-delete. Every member's activeOrgId pointing at it is cleared first (their sessions survive; they're just no longer scoped to a now-gone org).

{ "message": "Organization deleted" }

The only error beyond the standard auth ones is org_not_found (404).


curl

curl -X DELETE https://api.myapp.com/auth/orgs/a1b2... \
  -H "Origin: https://myapp.com" \
  -b cookies.txt -c cookies.txt

Programmatic (Go)

err := auth.Services.Org.DeleteOrg(ctx, goauth.DeleteOrgInput{OrgID: orgID, ActorID: callerUserID})

Client

See Delete organization for the wrapper and confirmation dialog.

Members

List members — GET /auth/orgs/{orgID}/members

Auth: org member. Query params, all optional:

ParamValuesDefaultNotes
roleowner, admin, member— (no filter)Anything else is a 400 — see the callout under List your orgs.
searchany string— (no filter)Substring match on the member's name or email.
orderByjoined_at, role, name, emailjoined_atUnrecognized values fall back to the default.
orderDirectionasc, descascSame fallback behavior.
offsetinteger0
limitinteger20Omit for the default page of 20. limit=0 is explicit unlimited — every member of the org, in one response. Other values are capped at 100. See the limit=0 callout under List your orgs — same semantics here.
{
  "members": [
    {
      "orgId": "a1b2...",
      "userId": "b3f1...",
      "role": "owner",
      "joinedAt": "2026-08-01T09:00:00Z",
      "user": { "...": "the full domain.User for this member" }
    }
  ],
  "limit": 20,
  "offset": 0
}

The total lives at GET /auth/orgs/{orgID}/members/count (accepts role and search, ignores pagination) so the list doesn't run a COUNT(*) per page.


curl

curl "https://api.myapp.com/auth/orgs/a1b2.../members?role=admin&search=jane&orderBy=name&orderDirection=asc&offset=0&limit=20" \
  -b cookies.txt -c cookies.txt

curl "https://api.myapp.com/auth/orgs/a1b2.../members/count?role=admin&search=jane" \
  -b cookies.txt -c cookies.txt
# → { "count": 1 }

Programmatic (Go)

result, err := auth.Services.Org.ListMembers(ctx, goauth.ListMembersInput{
    OrgID: orgID, ActorID: callerUserID, Offset: 0, Limit: nil, // nil = default 20; &0 = unlimited
    OrderBy: "name", OrderDirection: "asc",
})
// result.Members, result.Limit, result.Offset

n, err := auth.Services.Org.CountMembers(ctx, goauth.ListMembersInput{
    OrgID: orgID, ActorID: callerUserID, Role: &role, Search: &search,
})

Client

See List members for the wrapper and data table.

Remove a member, or leave — DELETE /auth/orgs/{orgID}/members/{userID}, POST /auth/orgs/{orgID}/leave

Two routes, one underlying operation: LeaveOrg is literally RemoveMember with your own ID — leaving is just removing yourself. Removing someone else requires org admin; leaving only requires being a member (you can always remove yourself).

{ "message": "Member removed" }

or, from /leave:

{ "message": "Left organization" }
CodeStatusCause
org_member_not_found404That user isn't a member
cannot_remove_last_owner400Target is the org's only Owner — removing (or leaving as) the last Owner is blocked so an org can never end up with zero owners

curl

# Remove someone else (admin+)
curl -X DELETE https://api.myapp.com/auth/orgs/a1b2.../members/b3f1... \
  -H "Origin: https://myapp.com" -b cookies.txt -c cookies.txt

# Leave it yourself
curl -X POST https://api.myapp.com/auth/orgs/a1b2.../leave \
  -H "Origin: https://myapp.com" -b cookies.txt -c cookies.txt

Programmatic (Go)

err := auth.Services.Org.RemoveMember(ctx, goauth.RemoveMemberInput{
    OrgID: orgID, UserID: targetUserID, ActorID: callerUserID,
})
// leaving is the same call with your own ID as UserID (the actor is you):
err = auth.Services.Org.LeaveOrg(ctx, goauth.LeaveOrgInput{OrgID: orgID, UserID: callerUserID})

Client

See Remove member and Leave organization for the wrapper and confirmation dialogs.

Change a member's role — PATCH /auth/orgs/{orgID}/members/{userID}/role

Auth: org admin — except granting or revoking owner, which requires the caller to already be an Owner (see the Roles callout above). Body: { "role" }.

{ "message": "Role updated" }
CodeStatusCause
invalid_role400role isn't owner/admin/member
org_member_not_found404Target isn't a member
org_forbidden403Caller is an Admin (not Owner) trying to grant or revoke owner
cannot_remove_last_owner400Target is the sole Owner and the new role isn't owner

curl

curl -X PATCH https://api.myapp.com/auth/orgs/a1b2.../members/b3f1.../role \
  -H "Content-Type: application/json" \
  -H "Origin: https://myapp.com" \
  -b cookies.txt -c cookies.txt \
  -d '{"role":"admin"}'

Programmatic (Go)

err := auth.Services.Org.UpdateMemberRole(ctx, goauth.UpdateMemberRoleInput{
    OrgID:   orgID,
    UserID:  targetUserID,
    NewRole: domain.OrgRoleAdmin,
    ActorID: callerUserID, // whose own membership gets checked for the Owner-only rule above
})

Client

See Change member role for the wrapper and role select component.

Active org

A session can be scoped to one org at a time — stored as sessions.active_org_id/active_org_role (see Schemas → sessions), returned on every session-listing response from Sessions. Your app reads it to decide which org's data the current request should see.

Tenant-safe application handlers

For routes that query tenant-owned application data, use RequireActiveOrgScope. It authenticates the request, checks that the session has an active organization with at least the required role, and invokes the handler with a non-empty goauth.OrgScope only after those checks succeed:

type ProjectStore struct {
    db *sql.DB
}

func (s *ProjectStore) Get(ctx context.Context, scope goauth.OrgScope, projectID string) (*Project, error) {
    const query = `
        SELECT id, name
        FROM projects
        WHERE org_id = $1 AND id = $2`

    var project Project
    err := s.db.QueryRowContext(ctx, query, scope.OrgID, projectID).
        Scan(&project.ID, &project.Name)
    return &project, err
}

func getProject(w http.ResponseWriter, r *http.Request, scope goauth.OrgScope) {
    project, err := projects.Get(r.Context(), scope, r.PathValue("projectID"))
    // handle err and encode project
}

mux.Handle(
    "GET /projects/{projectID}",
    auth.CORS(auth.RequireActiveOrgScope(domain.OrgRoleMember, getProject)),
)

RequireActiveOrgScope includes RequireAuth; do not wrap it a second time. It returns no_active_org (400) if the session has no active org and org_forbidden (403) if its role is below the required role. For a route that names the tenant in an {orgID} path segment, use RequireOrgScope with the same handler signature instead.

Org authorization does not rewrite your SQL

OrgScope makes the tenant binding an explicit handler and repository argument, but go-auth cannot inject org_id into queries issued by your application. Every read, update, and delete of tenant-owned data must include scope.OrgID in its predicate. Filtering only by a caller-controlled resource ID can expose another tenant's row, even when this middleware authorized the request itself.

The lower-level RequireActiveOrg(role) and RequireOrg(role) middleware remain available when a conventional http.Handler is required. Wrap them inside RequireAuth, then read the resolved tenant with middleware.GetOrgID(ctx) and middleware.GetOrgRole(ctx). They perform the same authentication and role checks, but recovering the org from context is easier to forget than accepting OrgScope directly, so prefer the scoped variants for tenant-owned data routes.

An explicit {orgID} and the session's active organization are independent selections and can legitimately differ. RequireOrgScope authorizes the path organization; RequireActiveOrgScope authorizes the active organization stored on the session. Choose one source for a route and use the resulting scope.OrgID all the way through its repository calls.

From a browser client, there's no dedicated "what's my active org" endpoint — /auth/me returns the user, not the session. The active org is visible on the session-listing responses (match currentSessionId against the sessions array to find your own), but the simplest approach is usually to just track it as local state the moment you call setActive/clearActive below, rather than re-fetching to confirm it.

Set active org — PUT /auth/orgs/active

Auth: any authenticated user. Body: { "orgId" }. No {orgID} path segment — membership is checked inside the service, not by RequireOrgMember middleware.

{ "message": "Active org updated" }

The only error beyond the standard auth ones is org_member_not_found (404) — you can't activate an org you don't belong to.


curl

curl -X PUT https://api.myapp.com/auth/orgs/active \
  -H "Content-Type: application/json" \
  -H "Origin: https://myapp.com" \
  -b cookies.txt -c cookies.txt \
  -d '{"orgId":"a1b2..."}'

Programmatic (Go)

err := auth.Services.Org.SetActiveOrg(ctx, goauth.SetActiveOrgInput{SessionID: sessionID, UserID: userID, OrgID: orgID})

Client

See Set active organization for the wrapper and org switcher component.

Clear active org — DELETE /auth/orgs/active

Auth: any authenticated user. No body.

{ "message": "Active org cleared" }

curl

curl -X DELETE https://api.myapp.com/auth/orgs/active \
  -H "Origin: https://myapp.com" -b cookies.txt -c cookies.txt

Programmatic (Go)

err := auth.Services.Org.ClearActiveOrg(ctx, goauth.ClearActiveOrgInput{SessionID: sessionID})

Client

The client has no dedicated clear-active wrapper — call the endpoint directly (see Setup).

Invites

Email-based, code-authenticated, and matched by email on accept — the accepting account's email must equal the invite's, so an invite can't be redeemed by whoever happens to have the link.

Creating an invite — POST /auth/orgs/{orgID}/invites

Auth: org admin — except inviting someone directly as owner, which requires the caller to already be an Owner, mirroring Change a member's role. Body: { "email", "role" }. Requires a mailer (WithMailer/WithEmail) — same requirement as email verification; without one, this fails outright rather than silently skipping delivery.

{
  "id": "c4d5...",
  "orgId": "a1b2...",
  "email": "new-hire@example.com",
  "role": "member",
  "rawCode": "the-raw-code",
  "invitedBy": "b3f1...",
  "expiresAt": "2026-08-16T12:00:00Z",
  "createdAt": "2026-08-09T12:00:00Z"
}

rawCode only appears here, on creation — List invites omits it, since by then it's only useful to whoever received the email.

CodeStatusCause
invalid_role400role isn't owner/admin/member
org_forbidden403Caller is an Admin (not Owner) inviting as owner
email_failed500Invite row was created, but sending the email failed
internal_error500InviteTTL isn't configured (WithOrganizations wasn't set up with a positive InviteTTL)

curl

curl -X POST https://api.myapp.com/auth/orgs/a1b2.../invites \
  -H "Content-Type: application/json" \
  -H "Origin: https://myapp.com" \
  -b cookies.txt -c cookies.txt \
  -d '{"email":"new-hire@example.com","role":"member"}'

Programmatic (Go)

invite, err := auth.Services.OrgInvite.CreateOrgInvite(ctx, goauth.CreateOrgInviteInput{
    OrgID:     orgID,
    Email:     "new-hire@example.com",
    Role:      domain.OrgRoleMember,
    InvitedBy: callerUserID,
})

Client

See Create invite for the wrapper and invite form component.

Accepting an invite — POST /auth/orgs/invites/accept

Auth: any authenticated user — the invited person has to already have (or just have created) an account; this only adds them to the org. Body: { "code" }. No {orgID} segment — the org comes from the code itself.

{ "message": "Invite accepted" }
CodeStatusCause
invalid_code400code was empty
org_invite_expired400Code doesn't match any invite, or matches one that's already been claimed or has expired — all three collapse to this one code
org_invite_email_mismatch400The authenticated caller's email doesn't match the invite's — log in (or sign up) as the invited address first

curl

curl -X POST https://api.myapp.com/auth/orgs/invites/accept \
  -H "Content-Type: application/json" \
  -H "Origin: https://myapp.com" \
  -b cookies.txt -c cookies.txt \
  -d '{"code":"the-raw-code"}'

Programmatic (Go)

err := auth.Services.OrgInvite.AcceptInvite(ctx, goauth.AcceptInviteInput{
    UserID:  callerUserID,
    RawCode: code,
})

Client

See Accept invite for the wrapper and accept form component.

Listing invites — GET /auth/orgs/{orgID}/invites

Auth: org admin. rawCode is never present here (see Creating an invite). Query params, all optional:

ParamValuesDefaultNotes
roleowner, admin, member— (no filter)The role the invite grants on acceptance, not the caller's own role. Anything else is a 400, as on every org listing.
statuspending, expired— (both)Derived from expiresAt vs. the current time — there's no stored status column. A claimed invite's row is deleted outright, so it never shows up here as anything.
searchany string— (no filter)Substring match on the invited email.
orderBycreated_at, expires_at, email, rolecreated_atUnrecognized values fall back to the default.
orderDirectionasc, descdescNote the default direction here is desc (newest first), unlike orgs/members above — matches the previous fixed ordering.
offsetinteger0
limitinteger20Omit for the default page of 20. limit=0 is explicit unlimited — every invite for the org, in one response. Other values are capped at 100. See the limit=0 callout under List your orgs — same semantics here.
{
  "invites": [
    {
      "id": "inv_abc...",
      "email": "new-hire@example.com",
      "role": "member",
      "status": "pending",
      "expiresAt": "2026-01-22T10:30:00Z",
      "createdAt": "2026-01-15T10:30:00Z"
    }
  ],
  "limit": 20,
  "offset": 0
}

The total lives at GET /auth/orgs/{orgID}/invites/count (accepts role, status, search; ignores pagination) so the list doesn't run a COUNT(*) per page.


curl

curl "https://api.myapp.com/auth/orgs/a1b2.../invites?status=pending&search=new-hire&orderBy=created_at&orderDirection=desc&offset=0&limit=20" \
  -b cookies.txt -c cookies.txt

curl "https://api.myapp.com/auth/orgs/a1b2.../invites/count?status=pending&search=new-hire" \
  -b cookies.txt -c cookies.txt
# → { "count": 1 }

Programmatic (Go)

result, err := auth.Services.OrgInvite.ListOrgInvites(ctx, goauth.ListOrgInvitesInput{
    OrgID: orgID, ActorID: callerUserID, Offset: 0, Limit: nil, // nil = default 20; &0 = unlimited
    Status: statusPtr("pending"), OrderBy: "created_at", OrderDirection: "desc",
})
// result.Invites, result.Limit, result.Offset

n, err := auth.Services.OrgInvite.CountOrgInvites(ctx, goauth.ListOrgInvitesInput{
    OrgID: orgID, ActorID: callerUserID, Status: statusPtr("pending"),
})

Client

See List invites for the wrapper and data table.

Resending or deleting an invite — POST .../invites/{inviteID}/resend, DELETE .../invites/{inviteID}

Auth: org admin for both. orgID in the path is only used for the membership/role check — the handler looks up the invite by inviteID alone, so it isn't re-validated against orgID a second time inside the handler itself.

{ "message": "Invite email resent" }

or:

{ "message": "Invite deleted" }

Delete here is a hard delete of the invite record — distinct from revoking a self-service signup invite (see Routes → Invites), which is a different feature with its own soft-cancel semantics.


curl

curl -X POST https://api.myapp.com/auth/orgs/a1b2.../invites/c4d5.../resend \
  -H "Origin: https://myapp.com" -b cookies.txt -c cookies.txt

curl -X DELETE https://api.myapp.com/auth/orgs/a1b2.../invites/c4d5... \
  -H "Origin: https://myapp.com" -b cookies.txt -c cookies.txt

Programmatic (Go)

err := auth.Services.OrgInvite.ResendOrgInviteEmail(ctx, inviteID)
err = auth.Services.OrgInvite.DeleteOrgInvite(ctx, inviteID)

Client

See Resend invite and Delete invite for the wrapper and confirmation dialogs.

Next

  • Routes — the full path/param table for every route on this page
  • Schemas — the organizations, organization_members, and organization_invites tables
  • Authentication — the mailer requirement invites share with email verification
  • Sessions — where activeOrgId/activeOrgRole show up on a session

On this page