Skip to content

feat: add team invites - #51

Merged
bjarn merged 2 commits into
mainfrom
feat/team-invites
Oct 21, 2026
Merged

bjarn merged 2 commits into
mainfrom
feat/team-invites

Conversation

@bjarn

@bjarn bjarn commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

What changed

Adds $lettermint->team->invites, a sub-client for pending team invitations. It sits next to team->members, works the same way and uses the team token:

Method Request Returns
list(array $query = []) GET /team/invites (['page' => ['size' => …, 'cursor' => …]]) ListTeamInvitesResponse (CursorPage<TeamInviteData>)
iterate(array $query = []) follows next_cursor Generator<int, TeamInviteData>
retrieve(string $inviteId) GET /team/invites/{inviteId} TeamInviteData
create(array $body) POST /team/invites with CreateTeamInviteData TeamInviteData (201)
update(string $inviteId, array $body) PUT /team/invites/{inviteId} with UpdateTeamInviteData TeamInviteData
revoke(string $inviteId) DELETE /team/invites/{inviteId} MessageResponse
  • create sends the invitation email. The team needs a free seat, and the Owner role can't be assigned.
  • update replaces the email, role and project access. A new email is sent only when the address changes.
  • Bodies are typed with the generated PHPStan array shapes (@phpstan-import-type), like members->updateAssignment.
  • Errors use the existing exception classes. A 409 throws ConflictException, with the conflict code in errorCode. A 422 throws ValidationException, with the field errors in errors. No new exception classes.
  • Empty, . and .. invite IDs are rejected with a LettermintConfigException before any request, as in the other methods.
  • None of these operations take an Idempotency-Key, so they have no $idempotencyKey argument.
  • Type check fix in Internal\Transport: with 71 operations, PHPStan stops tracking the literal type of the generated Operations::ALL table and flagged the auth value. The transport now narrows that value with a small match. Runtime behaviour is unchanged.
  • README: team->invites is in the Team API table, with a short example.
  • tests/phpstan/assertions.php covers the new return types.

The PRs stacked on top of this one add team role management and member removal. Until then, the coverage test in tests/TeamApiTest.php lists those six operations as pending. It still checks every other operation strictly, and it now expects 71 operations instead of 60.

Non-breaking. Suggested bump: minor.

API changes in the regenerated types

Change Where SDK impact
New GET /team/invites (cursor paginated: page[size], page[cursor]) ListTeamInvitesQuery, ListTeamInvitesResponse, TeamInviteData, TeamInviteDataRole team->invites->list / iterate (this PR)
New GET /team/invites/{inviteId} TeamInviteData team->invites->retrieve (this PR)
New POST /team/invites (201; also 404, 409, 422) CreateTeamInviteData, CreateTeamInviteDataProjectAccess team->invites->create (this PR)
New PUT /team/invites/{inviteId} (also 409, 422) UpdateTeamInviteData, UpdateTeamInviteDataProjectAccess team->invites->update (this PR)
New DELETE /team/invites/{inviteId} MessageResponse team->invites->revoke (this PR)
New GET /team/roles/{roleId} TeamRoleData team->retrieveRole (next PR)
New POST /team/roles (201; also 409, 422) CreateTeamRoleData team->createRole (next PR)
New PUT /team/roles/{roleId} (also 409, 422) UpdateTeamRoleData team->updateRole (next PR)
New DELETE /team/roles/{roleId} with optional replacement_role_id (also 409, 422) DeleteTeamRoleQuery, MessageResponse team->deleteRole (next PR)
New POST /team/roles/{roleId}/duplicate (201; also 409, 422) DuplicateTeamRoleData team->duplicateRole (next PR)
New DELETE /team/members/{userId} (also 409) MessageResponse team->members->remove (last PR)
GET /team/members takes the optional filter[project_id] and returns only the members who can access that project ListTeamMembersQuery gains filter?: array{project_id?: string} Already passes through team->members->list / iterate; tested in the last PR
TeamRoleData gains description (string|null, optional) TeamRoleData (also in team->roles() results) New read-only property; no code change
11 new entries Operations::ALL (60 → 71) None

No existing type loses or changes a field. No compatibility tool flags apply to PHP: the new TeamRoleData property is a @property-read on a magic object, so callers' code is unaffected.

Usage

$lettermint = new Lettermint(teamToken: getenv('LETTERMINT_TEAM_TOKEN'));

$invite = $lettermint->team->invites->create([
    'email' => 'jane@example.com',
    'role_id' => 'role-id',
    'project_access' => ['scope' => 'selected', 'project_ids' => ['project-id']],
]);

$lettermint->team->invites->update($invite->id, [
    'email' => 'jane@example.org',
    'role_id' => 'role-id',
    'project_access' => ['scope' => 'all'],
]);

foreach ($lettermint->team->invites->iterate() as $pending) {
    echo $pending->email, ' ', $pending->role->name, PHP_EOL;
}

$lettermint->team->invites->revoke($invite->id);

Checks

On this branch (PHP 8.5, Composer 2.10):

  • vendor/bin/pest --ci: 328 passed (3662 assertions)
  • vendor/bin/phpstan: no errors (level 10, including tests/phpstan/assertions.php)
  • vendor/bin/pint --test: passed
  • composer generate:check (full regeneration check): 170 generated files up to date
  • composer generate:check in the header-only mode that CI runs: headers of 170 generated files OK

The full matrix ran on the top of the stack (see the last PR).

Not run locally

  • The Windows jobs of the test matrix.

Suggested release tag

3.2.0 (minor). main already has unreleased features since 3.1.0, so this fits in the same minor release.

🤖 Generated with Claude Code

bjarn and others added 2 commits October 20, 2026 22:37
- Team invites: list (cursor paginated), retrieve, create, update and
  revoke (`/team/invites`, `/team/invites/{inviteId}`), with
  `TeamInviteData` and `ListTeamInvitesResponse`.
- Team roles: retrieve, create, update, delete (optional
  `replacement_role_id` query) and duplicate (`/team/roles/{roleId}`,
  `/team/roles`, `/team/roles/{roleId}/duplicate`).
- Team members: remove (`DELETE /team/members/{userId}`), and the member
  list takes an optional `filter[project_id]`.
- `TeamRoleData` gains an optional, nullable `description`.

Co-Authored-By: Claude <noreply@anthropic.com>
Add `$lettermint->team->invites` with `list`, `iterate`, `retrieve`,
`create`, `update` and `revoke` for pending team invitations.

The operation coverage test lists the role management and member
removal operations as pending; the next PRs in this stack add them.

Co-Authored-By: Claude <noreply@anthropic.com>
@bjarn
bjarn requested a review from a team as a code owner October 20, 2026 20:49
@bjarn
bjarn added this pull request to stack #54 October 20, 2026 20:49
@bjarn
bjarn merged commit 0a98259 into main Oct 21, 2026
17 checks passed
@bjarn
bjarn deleted the feat/team-invites branch October 21, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants