Developer User Graph and Invitations

Target reader: backend and console developers modeling organizations, projects, and team access.

Alpha accounts have a user_type of developer or system_admin. Developers can register, log in, create organizations, create projects, and generate Project Keys; a Project Key can use Public API GET /v1/sdk/config, POST /v1/feedback/reports, and the consent-gated attachment route, but cannot access /admin/v1/*. Account type and tenant authorization are independent, and organization and project access always comes from membership.

Minimal example

flowchart LR
  User["authenticated user"] --> UserType["user_type"]
  UserType --> Developer["developer"]
  UserType --> SystemAdmin["system_admin"]
  User --> OrgMembership["organization membership"]
  User --> ProjectMembership["project membership"]
  OrgMembership --> Organization["organization"]
  ProjectMembership --> Project["project"]
  Organization --> Project["project"]
  SystemAdmin --> SystemUsers["GET /admin/v1/system/users"]
  Project --> ProjectKey["Project Key"]
  ProjectKey --> PublicAPI["Public API SDK config and feedback writes"]
  Project --> FeedbackReport["feedback_report"]

The AdminUser contract has user_type values system_admin and developer, and user statuses active, disabled, and pending_verification. A system administrator's only additional system-wide capability is the read-only /admin/v1/system/users endpoint. It is not a tenant role and does not automatically expose any organization or project. When a system administrator joins a tenant, responses carry their real membership role; without membership, targeted requests for that tenant return 403.

Organization and Project responses must carry a membership role, and the only values are owner, admin, developer, qa, and viewer. A member's account user_type can be developer or system_admin, but it never changes that role. The E2E matrix verifies developer A creates the org/project/key, developer B joins by invitation and can read the project, and developer C is denied.

GET /admin/v1/developer-graph is a navigation aggregate that returns the current user plus all visible organizations, projects, organization memberships, and direct project memberships together. It is not a canonical list GET, does not accept limit/offset, and does not use the { items, offset, limit, total, has_more } envelope. The separate organization, project, and member collection endpoints still use the pagination contract.

Invitations are part of the canonical Alpha management API. The console creates and lists organization invitations through /admin/v1/organizations/{organization_id}/developer-invitations. The inviter copies only the one-time token and does not create a credential-bearing link. The recipient opens the credential-free /invitations/accept page, signs in or registers if needed, then pastes the token after returning. The token exists only in the current form and is sent in the JSON body to /admin/v1/invitations/accept; it must not enter a URL, return_to, browser storage, or logs.

An invitation without project_id creates organization membership and appears in the developer graph memberships array. An invitation with project_id creates project membership only and appears in project_memberships; the accepted developer can access that project, feedback, attachments, and comments, but cannot read organization detail and is not listed as an organization member.

Organization-scoped invitation:

curl -X POST "$ADMIN_API/admin/v1/organizations/$ORG_ID/developer-invitations" \
  -H "Authorization: Bearer $TOKEN_A" \
  -H "Content-Type: application/json" \
  -d '{"email":"developer-b@example.com","role":"developer"}'

curl -X POST "$ADMIN_API/admin/v1/invitations/accept" \
  -H "Authorization: Bearer $TOKEN_B" \
  -H "Content-Type: application/json" \
  -d '{"token":"enb_inv_..."}'

Project-scoped invitation:

curl -X POST "$ADMIN_API/admin/v1/projects/$PROJECT_ID/invitations" \
  -H "Authorization: Bearer $TOKEN_A" \
  -H "Content-Type: application/json" \
  -d '{"email":"developer-d@example.com","role":"qa"}'

curl -X POST "$ADMIN_API/admin/v1/invitations/accept" \
  -H "Authorization: Bearer $TOKEN_D" \
  -H "Content-Type: application/json" \
  -d '{"token":"enb_inv_..."}'

After the project-scoped accept, GET /admin/v1/projects/{project_id} succeeds for the invited developer, while GET /admin/v1/organizations/{organization_id} returns 403. Project invitation list and revoke responses must not include accept_token; only the create response shows it once. Revoke responses include revoked_by_user_id and revoked_at, and invitation audit metadata must not include token values.

The Alpha verification matrix uses Developer B for organization-scoped invitation, Developer D for project-scoped invitation, and Developer C as the denied user.

Common mistakes

Next step

Use Troubleshooting when the local path does not work.