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/README.md.

Project members and invitations

The default Go API serves these reads through the Project owner. The development fixture mode remains available through explicit configuration. Generated clients and disposable fixture checks do not establish support in a deployed backend.

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 under the same current owner permission 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.

The retained Project page uses asserted_name for every member, including the viewer. Public profile visibility does not affect this authorised list. Its own-profile read identifies the You marker only. The browser regression runs with private/absent public profiles, independent name revisions, unavailable reads and a delayed response from a previously selected Project:

pnpm exec playwright test --config acceptance/browser/project-members.config.ts

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 source schemas are members.paths.json, invitations.paths.json and schemas.json. Regenerate with pnpm generate:contracts. Both generated clients export:

import {
  getProjectMembers,
  listProjectInvitations,
} from "@/adaptors/generated/http/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. Sources are services/web/src/adaptors/generated/http/project/project.ts and compat/themelio/services/web/src/adaptors/generated/http/project/project.ts. The canonical web client also exports useGetProjectMembers and useListProjectInvitations. Query-cache values must be cleared when the Party session or Project access changes; HTTP no-store is not a React Query cache policy.

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. Error bodies contain code: invalid_input for 400, unauthorised for 401, not_found for 404 and temporarily_unavailable for 503. Keep the two sections' loading/error states independent. An invitation read failure does not invalidate a successful member read.

Reconcile the API-auth credentials and deploy the Waldur actor capability before the new Project owner, then deploy the new API and enable frontend use of the new fields. The invitation adaptor guide describes the scoped credential, URL and trust requirements. The established owner member query retains its original wire shape for existing API instances. The new API calls GetProjectMemberDetails; an older owner cannot satisfy that query and the read fails unavailable. Its reply preserves presence of membership_complete, and the API rejects absent presence. For real data, leave API_PROJECT_READ_FIXTURES unset or false and remove both fixture scenario settings. An unavailable owner or provider produces an error; the API never substitutes fixture data. Clear any cached fixture responses when switching the development frontend to real reads. Deploy backend support before relying on the new required fields. During 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.

Page source

Source snapshot ยท Revision f928834c8.