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
- Do not let a project key read feedback or manage users.
- Do not bypass tenant membership checks based on
user_type. - Do not create invitation URLs containing tokens; share the one-time token and the generic
/invitations/acceptpage separately. - Do not store invite tokens in logs or expose them in list responses.
- Do not send pagination parameters to the Developer Graph or parse it as a list envelope.
Next step
Use Troubleshooting when the local path does not work.