For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt, and this page is available as Markdown at /reference/contracts/project/http_api/member-invitation-preview/README.md.

Project member and invitation contract preview

Development fixture contract. This standalone OpenAPI specification and schemas let frontend work proceed with generated clients and fixture responses. The same shapes are now present in the active HTTP contract; the isolated preview client and fixture assets remain available for development.

The preview supplies contract sources, two isolated Orval outputs and Go API fixture bindings. An explicitly configured development API serves these examples through the normal HTTP/session path. The default API uses the real Project owner reads. The field names are agreed independently of the broader #1066 naming work.

Both reads require a current Party identity and a canonical Project UUID. Browsers use the Party session; the enrolled MCP gateway can use its operation-bound assertion. The preview retains the active contract's authentication declarations and current owner checks. Responses use Cache-Control: private, no-store. Neither operation accepts query parameters. These reads add no mutation, realtime subscription or automatic polling.

Members

GET /api/v1/projects/{projectId}/members, operation getProjectMembers, requires current project.view authority. The backend checks Project/source authority before and after the read.

{
  "project_id": "11111111-1111-4111-8111-111111111111",
  "revision": 12,
  "items": [
    {
      "party_id": "22222222-2222-4222-8222-222222222222",
      "roles": ["project-administrator"],
      "asserted_name": "Alex Morgan",
      "asserted_name_revision": 7
    },
    {
      "party_id": "33333333-3333-4333-8333-333333333333",
      "roles": ["developer"],
      "asserted_name": null,
      "asserted_name_revision": null
    }
  ],
  "pending_member_count": 1,
  "membership_complete": false
}

All properties are required. asserted_name and asserted_name_revision are nullable. The name is the existing trusted AAI identity projection's name; editable profile names and email addresses are never used as fallbacks. A current projection with no name has a null name and its non-null revision. A pending, superseded or currently ineligible identity projection has both fields null. A missing name preserves the membership row and roles. Infrastructure or corrupt-authority failures fail the whole query.

revision versions Project membership. asserted_name_revision independently versions the Party identity projection and must not be compared between Parties. An unchanged Project revision does not imply unchanged names.

items is ordered by party_id. The complete source observation contains at most 256 member bindings; several qualified bindings can contribute to one Party row. Every observed binding is resolved into a row or counted in pending_member_count. membership_complete equals pending_member_count === 0. This flag concerns membership resolution; it says nothing about missing names. The count is diagnostic information and must never be labelled as invitations. Show available rows and a simple incomplete-membership state when the flag is false. Use a neutral missing-name label or shortened canonical Party ID.

Invitations

GET /api/v1/projects/{projectId}/invitations, operation listProjectInvitations, requires current project.access.manage authority, currently granted to Project administrators. Show this separate section only to those administrators.

{
  "project_id": "11111111-1111-4111-8111-111111111111",
  "project_revision": 12,
  "read_at": "2026-11-06T12:00:00Z",
  "items": [
    {
      "invitation_id": "44444444-4444-4444-8444-444444444444",
      "recipient_email": "new.member@example.invalid",
      "roles": ["developer"],
      "state": "pending",
      "expires_at": "2026-11-20T12:00:00Z"
    }
  ]
}

All properties are required and non-null. invitation_id is an opaque reference, not a canonical Party ID or acceptance credential. The backend derives this scoped reference without exposing the provider invitation locator. recipient_email is available only through this administrator-authorised read and is not an asserted name. Roles use the same canonical values as members.

This slice returns only provider-pending invitations whose expiry is after read_at. It excludes accepted, cancelled, rejected and expired invitations. There is no historical invitation list or acceptance/revocation operation. expires_at is the provider's actual expiry, not a locally invented lifetime.

The backend resolves the exact canonical Project/provider binding, traverses all provider pages and checks Project scope and customer itself. It rechecks current administrator authority after acquisition. items contains the complete result, bounded to 256 invitations and sorted by expires_at, then invitation_id. There is no cursor. Overflow, missing pages, unsupported roles and malformed provider data fail the whole request; partial results are never returned.

project_revision versions the Project authority used for this read, not the invitation collection. read_at is successful acquisition completion time. The provider supplies no atomic collection revision; this timestamp does not establish a transactional snapshot across provider pages.

Clients, errors and rollout

The existing Orval configuration generates two preview clients from this same specification. The retained frontend uses plain fetch functions; services/web also receives TanStack Query v5 hooks, query keys and options.

Both preview clients export:

import {
  getProjectMembers,
  listProjectInvitations,
} from "@/adaptors/generated/project-member-invitation-preview/project/project";

const members = (await getProjectMembers(projectId)).data;
const invitations = (await listProjectInvitations(projectId)).data;

The import is relative to the active frontend's src alias. Outputs are under services/web/src/adaptors/generated/project-member-invitation-preview/ and compat/themelio/services/web/src/adaptors/generated/project-member-invitation-preview/. The canonical web client also exports useGetProjectMembers and useListProjectInvitations; these need its existing QueryClientProvider. Compat has no TanStack dependency or provider and can keep using fetch functions. Query-cache values must be cleared when the Party session or Project access changes; HTTP no-store is not a React Query cache policy.

The generated clients make ordinary HTTP requests. Start the Go API with its normal runtime environment loaded, including Valkey, broker and AAI/session configuration, and enable the preview on that API process:

APP_ENV=development \
API_PROJECT_READ_FIXTURES=true \
API_PROJECT_MEMBERS_FIXTURE=members \
API_PROJECT_INVITATIONS_FIXTURE=invitations \
PHAROS_GO_PROJECT=services/api \
python3 scripts/go.py run ./services/api

Use the normal frontend origin and route its /auth and /api/v1 requests to this API instance through the existing development proxy. Sign in normally and select a canonical Project returned by GET /api/v1/me/projects. Browser requests carry the existing pharos_session cookie. Server-rendered reads use the existing server adaptor to forward that cookie. The frontend needs no mock installation, extra request parameters or fixture credential.

Fixture mode overlays only these two operations. The API authenticates the current Party and asks the existing Project owner for project.view or project.access.manage before returning sample data. The broker and existing Project/Party authority must be available; the new member-name and invitation backend queries are unnecessary for this mode. Operator/CLI credentials cannot replace a Party session. Denied or stale authority produces the normal error.

API_PROJECT_READ_FIXTURES defaults to disabled. Only the literal true enables it, and only with APP_ENV=local or development. Invalid flags/scenarios fail startup. The API logs that Project read fixtures contain synthetic data. Scenario settings are server configuration and require an API restart to change.

Member scenarios are members (named and unnamed rows with incomplete membership), members-empty and members-name-absent (a current projection with no name). Invitation scenarios are invitations and invitations-empty. Either section can independently use status 401, 404 or 503 to exercise its error state after the real permission check. Omitting a scenario selects members or invitations. Supplying scenarios while fixture mode is disabled fails startup.

The JSON examples remain the source of truth. Generation copies them into the API binary, which validates them against the embedded preview schema during startup. project_id is replaced with the requested authorised Project. Invitation project_revision comes from the real permission decision; read_at is the current fixture response time, and each expiry preserves the example's remaining lifetime. Names, roles, member revisions, counts and invitation contents remain synthetic; they make no claim about that Project's actual contents. The canonical web generation also retains its standard Orval MSW/Faker test outputs.

StatusMeaningFrontend handling
200Successful bounded readEmpty invitations prove an empty invitation list; an empty roster proves emptiness only when membership_complete is true.
400Invalid Project ID or any query parametersFix the request.
401Missing or no longer current Party sessionFollow the existing sign-in/session flow.
404Missing or inaccessible Project for this operationShow unavailable/access state; do not reveal whether the Project exists.
503Stale/changing authority, unavailable dependency, incomplete/oversized acquisition or invalid responseShow unavailable/retry state, retaining no claim that the list is empty.

The generated fetch adaptor rejects non-success responses with the existing HTTPError; inspect its status. Keep the two sections' loading/error states independent. An invitation read failure does not invalidate a successful member read.

With fixture mode disabled, both operations use the real owner queries and the agreed contract. There is no automatic fixture fallback on an owner failure. Enable the new frontend feature only against the fixture API or a backend deployment that supports the agreed contract.

The implementation preserves the existing member query/reply wire shape and adds a distinct owner query for member details, allowing the new owner to run with the old API. The new API rejects an unsupported owner response. Follow the active contract's deployment order, then disable fixture mode and remove the two scenario settings. The frontend keeps the same operation calls, paths and payloads; its preview client continues to work against the implemented API. When consolidating generated outputs, switch the import to @/adaptors/generated/http/project/project and remove the preview. Clear cached fixture data or reload the development session during this switch. In a mixed-version rollout, absent fields are unsupported-contract evidence, not equivalent to null, false or zero. An old backend's invitation-route 404 must not be converted into an empty invitation list. Frontend fixture work can proceed against fixtures/members.json, fixtures/invitations.json and fixtures/invitations-empty.json; these are contract examples, not live deployment evidence.

Regeneration and verification

From the repository root, with frozen pnpm dependencies installed:

pnpm generate:contracts

Normal contract generation includes the preview: it validates OpenAPI and all five examples, copies the Go API assets and runs the pinned Orval configuration into both preview directories alongside the active HTTP and Protobuf outputs. A contract regression checks equality of the active and preview HTTP shapes. For a focused regeneration, run uv run --no-project --with openapi-spec-validator==0.7.2 python contracts/project/http_api/member-invitation-preview/generate.py. Add --check to compare all three generated output sets. pnpm verify includes this drift check in its contracts group.

Exercise the HTTP bindings, owner-decision validation and development composition:

PHAROS_GO_PROJECT=services/api python3 scripts/go.py test \
  ./adaptors/http ./adaptors/openapi ./services/api

The real Valkey session lane (pnpm test:party:sessions) covers fixture HTTP access through the normal session/AUP checks and revocation. Contract examples, HTTP tests and disposable infrastructure qualification remain separate from live member-name/provider behaviour. Once the implementation is deployed and frontend consumers use the active client, remove the preview sources, generation/check hooks, API fixture bindings/configuration and generated outputs.

Page source

Source snapshot ยท Revision f928834c8.