Organization memberships and applications

GET or POST https://sso.user.m7.org/userinfo/orgs

This M7 extension is separate from standard /userinfo. Request the registered OAuth scope orgs explicitly. Consumer authorization requires hosted consent; client-credentials grants use the application's registered scope policy. An orgs-only token can call this endpoint; standard UserInfo still requires openid. Omission of scope does not implicitly grant orgs. Active consumer users and ordinary client-credentials applications can discover their own Management memberships across organizations. Validated personal machine-owner proxies can discover their current registered owner's direct Management memberships. Tenant member tokens are ineligible. Tenant identity/groups remain fenced to their own directory. This is a caller-requested lookup of existing organization facts. SSO does not push organizations into a consuming server or grant that server access to its resources. Device authorization requires the same permission through its hosted device consent page after authentication. See the normative provider profile.

Request and trust

Resolve m7_orgs_endpoint from trusted exact-issuer discovery. For an unbound token:

curl --fail-with-body --silent --show-error \
  --request POST 'https://sso.user.m7.org/userinfo/orgs' \
  --header 'Authorization: Bearer ACCESS_TOKEN'

Use the UserInfo request contract for owning client_id assertions, token transport, fingerprint and sender bindings. The provider derives the client from the token and checks live token/session/account state, registered scope/audience policy, and active organization ownership of a client where relevant. No separate SSO resource audience is added by this endpoint. The credential must be an access token; an ID token, refresh token, authorization code or API key does not authorize this lookup. Verify the response sub exactly against the already validated user subject or, for an ordinary app, its public Client ID.

For DPoP use Authorization: DPoP ACCESS_TOKEN and a fresh DPoP proof binding this endpoint's exact URL, actual GET/POST method and access token ath; a proof for /userinfo or API BigFS cannot be reused. For certificate-bound tokens, present the bound certificate at the top-level m7_mtls_orgs_endpoint from discovery. It is an M7 extension outside the standard mtls_endpoint_aliases object. When both bindings apply, both are required. See discovery.

Current projection

{
  "sub": "22222222-2222-4222-8222-222222222222",
  "client": null,
  "orgs": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "principal_id": "9D91E1C7-700E-55C0-91ED-EA9C7B1F7D98",
      "name": "Example",
      "slug": "example",
      "access_level": "member",
      "consumer": [
        {
          "id": "66666666-6666-4666-8666-666666666666",
          "client_id": "docs-client",
          "name": "Docs"
        }
      ],
      "tenant": [],
      "management_groups": [
        {
          "id": "33333333-3333-4333-8333-333333333333",
          "name": "Management",
          "membership_id": "44444444-4444-4444-8444-444444444444",
          "role": "member",
          "pub": {
            "quota": 7
          },
          "tags": [
            {
              "id": "55555555-5555-4555-8555-555555555555",
              "name": "BigFS read",
              "slug": "bigfs-read",
              "data": {
                "actions": [
                  "read"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}

These UUIDs are valid illustrative identifiers; replace them with actual records. id remains the native organization UUID used by API User and SSO. principal_id is the organization's stable canonical M7 reference for principal lists, rosters, and external resource ownership. It does not replace the native org ID in ordinary org, directory, session, or token operations. The example principal ID is illustrative; consumers must use the verified provider value rather than derive their own.

The shared org service registers this reference at creation and ensures it again when an authorized org projection is exported, covering older organizations. Both paths use the existing native org UUID as source, source_kind=org, and a nil UUID tenant in the central registry. Repeated ensures preserve the same ID and existing registry metadata. Registration does not create list or Management membership, renew a lease, grant delegation, or authorize a user or app to act for the org. Existing disclosure and access checks still apply. A failed ensure fails the response through its existing operational error contract.

Each org currently has one implicit Management group. This response does not introduce arbitrary management-group creation. access_level and membership role are the caller's live stored owner, admin or member role. The list is sorted by org ID and can be empty (orgs: []). Only eligible active, non-archived orgs, active Management groups/principals and matching current user or application memberships survive the live lookup. A departure or removed membership is omitted on the next lookup.

client describes the OAuth registration that issued this token, independently of the user's orgs list. A personal consumer registration returns {"org":null,"tenant":{"id":"00000000-0000-0000-0000-000000000000","slug":"m7-identity-consumer"}}. For an org-owned registration, the owning org’s separate client_org_userinfo_disclosure setting controls whether client contains org: {id, slug, principal_id} and tenant: null (consumer app) or tenant: {id, slug} (tenant app). A policy-hidden org-owned registration returns client: null; it does not change which Management memberships appear in orgs. The exact modes are always, acl, and never (the default). always permits an otherwise eligible registration summary; never hides it; acl applies the owner’s ordered discovery policy to the calling registration, or the authenticated recipient on /introspect/orgs. This setting is independent of both the membership-list disclosure policy and client_org_release, which controls newly issued token ownership descriptors. An allowed client summary does not add the caller to an organization. The owning-org reference is ensured only after disclosure is allowed. Personal consumer markers and tenant directory descriptors retain their native ID shapes.

The org owner also controls disclosure to the requesting OAuth registration. SSO resolves its row id and current owning org from the validated token's client_id; request-supplied identities and token tenant cannot select the policy identity. Requesting apps may be personal or org-owned. The caller's owner/admin/member role does not bypass disclosure policy.

For each eligible membership, SSO evaluates the org's ordered app/org ACL; the first matching rule wins, without app-specific precedence. App rules match the registration row UUID, and org rules match its non-nil owning org. With no match, a requester owned by the disclosed org uses same_org_discoverable; other requesters use discoverable. Both switches default off, including on existing orgs with no saved policy. An explicit deny overrides the switches. Only stored org owners can manage these settings through API User and the User M7 Access → Policy tab.

Denied orgs are omitted entirely before loading public membership data, tags or app inventories. An allow rule permits disclosure of existing membership facts; it cannot create membership or grant product access. Invalid policy or failed storage fails the response. SDK and consuming applications receive the same projection shape, filtered by this live policy; a successful empty list may mean no memberships are discoverable to this app.

For a consumer token, sub is the user UUID. For an application token, sub is its public client_id; the internal Management principal source is the OAuth registration's row UUID. The provider authenticates the app as itself and checks its owning org remains active, when it has one. App access additionally requires at least one live human Management member in each returned org, matching API User's application-access rule. The creator uid and owning org do not grant Management membership. Add the app explicitly to Management to grant access; its own membership supplies its role, pub and tags. Ordinary apps may back either directory and may hold memberships in orgs other than their owning org; tenant identity binding is validated independently of those memberships.

Personal owner proxies are a separate, explicit case. The token must carry the issuer-controlled boolean machine_owner_proxy: true, retain the machine's public client_id as its subject, and pass live token and registration checks. The registration must be active and unexpired, permit client_credentials, have admin_application: yes, and have nil org and tenant. SSO resolves the active owner from that registration's current uid, never an owner claim or request parameter. The validated effective user and registered owner must agree. Nested act or m7_delegation credentials are rejected.

The proxy's response sub remains the machine's public Client ID. Only the internal Management lookup uses the owner's user UUID; the owner's direct membership supplies the role, pub and tags. Missing owner membership is not replaced by the machine's membership, root role, or transitive delegation. Disclosure remains independent: direct /userinfo/orgs calls use the calling registration, while recipient-authenticated /introspect/orgs calls use the receiving service's trusted registration. Allowing the calling proxy in the disclosure policy does not allow an unrelated receiving service. The receiving service's separate owner-proxy ACL still requires existing memberships for both the machine and its owner; this org lookup neither enrolls nor renews those memberships.

Check What it establishes
Registration org Who owns/manages the app; not the caller's memberships
Caller Management membership The live role, membership pub and assigned tags in this org
Owner discovery policy Whether those facts may be disclosed to this requesting app
Consuming-service policy Whether the authenticated caller may operate on a bucket or other resource

Both consumer and tenant are always arrays, including when empty. They list live app registrations owned by each returned org, not users, tenant-account memberships or the caller's memberships in those apps:

  • consumer: oauth_clients.org = org.id and nil tenant.
  • tenant: oauth_clients.org = org.id and non-nil tenant.

Only active, unarchived, unexpired registrations appear, sorted by row UUID. Each descriptor contains id (row UUID), client_id (public OAuth identifier) and name. OAuth registrations have no slug; client_id need not be a UUID. Personal apps (org nil) are excluded even when created by the caller. Secrets, creator IDs, configuration and notes are excluded. Inventory shows which apps share ownership; it does not grant those apps access to each other or substitute for a caller's Management membership.

Data Meaning
Membership pub organization_manage_group_meta.pub, with asset equal to the caller's Management membership UUID.
Tag data JSON on this individual tag definition, shared by assignments in its org's separate tag pool.
Organization data Separate organization-level data; read through authorized API User org properties, not this projection.

Tags use scope organization_management / org UUID and assignment asset organization_management_member / membership UUID, not the global principal. pub and tag data accept object/array/null. Missing public data is null; no assignments gives tags: []. Current associative PHP decoding serializes empty objects as []. The SDK preserves service-defined JSON contents without giving them universal product permission semantics.

Other principals' memberships, pri, internal owner/principal/creator fields, timestamps, assignment metadata and expiry fields are excluded. Membership expiry was not added. Public means exportable through this authorized call, not anonymous org access. Storage failures return an error, never a successful empty list.

Response policy and failures

HTTP 200 normally uses application/json. Registered UserInfo signing policy returns application/jwt with RS256/RS512 signature, exact provider issuer, client-ID audience and subject; registered encryption applies to that signed response. Decrypt/authenticate first, then verify signature, issuer, audience, time and exact subject. Policy, signer or encryption failures never fall back to plaintext. See response encryption. Responses are non-cacheable (Cache-Control: no-store).

Missing orgs returns HTTP 403 insufficient_scope and a required-scope challenge. Invalid/inactive tokens, ineligible subjects or sender-binding failures follow the UserInfo error contract, including invalid_token and invalid_dpop_proof. Operational lookup/signing failures use a safe server_error; do not turn them into empty authorization facts. The current SSO profile does not issue DPoP nonces, while SDK reports preserve a nonce challenge if an upstream deployment supplies one for explicit caller recovery.

Receiving-service organization lookup

Current source provides POST https://sso.user.m7.org/introspect/orgs for a confidential receiving service. Deployment and live acceptance of this route remain unverified here. It returns the same caller-bound organization facts as /userinfo/orgs, using the receiving registration for disclosure decisions and response signing/encryption. Caller-oriented discovery cannot establish disclosure permission to a different service.

Authenticate the recipient with its configured confidential method: Basic, Post, secret JWT, private-key JWT, PKI mTLS, or self-signed mTLS. The introspection client-authentication contract applies, including assertion audience/time/replay checks and IP policy. Public none clients cannot call this route. Both the recipient and its owning organization, when present, must remain active.

Send an application/x-www-form-urlencoded body:

Field Contract
token Required caller access token, carrying orgs. It identifies the caller, independently of recipient client authentication.
caller_client_id Optional exact-match assertion of the token’s requesting client_id; never selects another caller.
fingerprint Required when the caller token has the corresponding fingerprint binding.
Client authentication fields As required by the recipient’s registered method; body client_id identifies the recipient.

Example for a confidential recipient and an unbound caller token:

curl --fail-with-body --silent --show-error \
  --request POST 'https://sso.user.m7.org/introspect/orgs' \
  --user 'RECEIVER_CLIENT_ID:RECEIVER_CLIENT_SECRET' \
  --data-urlencode 'token=CALLER_ACCESS_TOKEN'

The recipient authentication header does not authenticate the caller token. Caller DPoP still needs a fresh matching-key proof for POST and this exact endpoint URL, including the token’s ath. A resource-request proof cannot be replayed here. Certificate-bound caller tokens require their bound certificate on the actual verified TLS connection; recipient client authentication does not remove that requirement. Tokens carrying both bindings require both.

SSO checks the caller token’s live status and exact signed resource audience against the recipient’s registered resource audiences. The receiver Client ID need not appear in aud; an arbitrary submitted audience grants no access. The caller must retain its own qualifying direct Management membership (or the registered owner’s membership for a validated personal owner proxy). Each organization must separately allow disclosure to the recipient registration.

HTTP 200 returns sub, client, orgs, and receiver_client_id, which must equal the authenticated recipient’s public Client ID. sub remains the caller subject. The client summary describes the caller registration, filtered by recipient disclosure; it is not the recipient registration. The recipient’s registered UserInfo policy determines JSON, signed JWT, or encrypted signed JWT delivery. Verify recipient audience and exact caller subject after any decryption and signature verification. Treat the result as fresh facts, then apply local resource and action authorization.

Missing token or invalid caller assertion returns HTTP 400 invalid_request; caller mismatch, missing orgs, or recipient-audience mismatch returns HTTP 403 insufficient_scope. Inactive/ineligible callers and sender-binding failures use the existing token/DPoP errors. Invalid client authentication, recipient eligibility, IP policy, method, lookup, and response-policy failures retain their existing OAuth error contracts. Errors are never empty successful facts. Owner denial can legitimately produce orgs: [].

This route is separate from standard token introspection and the receiver’s principal-list ACL decision. No new discovery extension or live mTLS alias is asserted for this route; verify listener availability and the installed SDK version separately.

Integration and rollout

Use current-source SDK acquisition or the website integration. Published Token/PHP 0.1.4 supports basic Organizations and Emails lookups. Canonical principal_id handling and receiving-service organization lookup require updated SDK source or the locally built and verified 0.1.5-rc.1 candidate. That candidate is not a public release; verify the installed version and endpoint availability. The updated SDK performs strict nested validation and exact subject binding. Seeing an org or a tag does not grant bucket, blog or other product access: consuming services define scope semantics and authorize resources separately. Reacquire facts when needed instead of persisting a membership snapshot as authority.

Both public discovery documents advertised orgs, m7_orgs_endpoint and m7_mtls_orgs_endpoint with identical metadata at 2026-09-27 02:11 UTC. Current-source projection and validation checks passed locally; live acceptance of the membership/application projection remains unverified here. Install API User's existing membership/tag storage before enabling this reader; SSO installs no org tables. See upgrade order.