Repository navigation
feat: add team invites - #40
Merged
Merged
Conversation
- New team invite operations (team token): GET /team/invites (cursor
paginated), GET /team/invites/{inviteId}, POST /team/invites (201),
PUT /team/invites/{inviteId} and DELETE /team/invites/{inviteId}.
New TeamInviteData, CreateTeamInviteData, UpdateTeamInviteData,
ListTeamInvitesQuery and ListTeamInvitesResponse types.
- New team role operations (team token): GET /team/roles/{roleId},
POST /team/roles (201), PUT /team/roles/{roleId},
DELETE /team/roles/{roleId} with an optional replacement_role_id query
(DeleteTeamRoleQuery) and POST /team/roles/{roleId}/duplicate (201).
New CreateTeamRoleData, UpdateTeamRoleData and DuplicateTeamRoleData.
- New DELETE /team/members/{userId} (team token) removes a non-Owner
member.
- The delete and revoke operations return a MessageResponse; the role,
invite and member-removal operations can answer 409 with an
RbacConflictException.
- GET /team/members gains an optional filter[project_id] query
(ListTeamMembersQueryFilter).
- TeamRoleData gains an optional, nullable description.
Co-Authored-By: Claude <noreply@anthropic.com>
- New team.invites sub-client (TeamInvites / AsyncTeamInvites, exported for type hints) with list(query), iterate(query), retrieve(invite_id), create(body), update(invite_id, body) and revoke(invite_id). Every method uses the team token and sends no Idempotency-Key header. - create() emails the invitation; update() replaces the email address, role and project access and sends a new email only when the address changes; revoke() returns a MessageResponse. - A 409 raises ConflictError with the error code; a 422 raises ValidationError with the field errors. - The coverage test lists the team role and member-removal operations as pending; the next PRs in this stack add them. - Document team.invites in the README and test the requests, the token, the bodies, the parsed responses, the invite ID checks, the cursor pagination and the 409 and 422 errors. Co-Authored-By: Claude <noreply@anthropic.com>
bjarn
added this pull request to stack #43
October 20, 2026 20:47
bjornftw
approved these changes
Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Non-breaking. Adds the
team.invitessub-client and regenerates the types from the latest API spec. Suggested version bump: minor.The team role methods and team member removal are added by the PRs stacked on top of this one. Until they land, the coverage test in
tests/test_surface.pylists those six operations as pending; every other operation is still checked.team.invites(TeamInvites/AsyncTeamInvites, exported for type hints):list(query=None)anditerate(query=None): pending invites, cursor paginated withpage[size]andpage[cursor], liketeam.members.retrieve(invite_id)create(body): invites a person with a role and project access and emails the invitation. The team needs a free seat; the Owner role can't be assigned.update(invite_id, body): replaces the email address, role and project access. A new email is sent only when the address changes.revoke(invite_id): returns aMessageResponse.timeout. None takes an idempotency key.409raisesConflictErrorwith the error code (for examplestale_resource); a422raisesValidationErrorwith the field errors. Empty or..invite IDs raiseLettermintConfigErrorbefore any request.API changes in the regenerated types
GET /team/invitesListTeamInvitesQuery,ListTeamInvitesResponse(CursorPage[TeamInviteData])GET /team/invites/{inviteId}TeamInviteDataPOST /team/invitesCreateTeamInviteData {email, role_id, project_access {scope, project_ids?}}; 201TeamInviteData; 409RbacConflictException, 422PUT /team/invites/{inviteId}UpdateTeamInviteData(same shape); 200TeamInviteData; 409, 422DELETE /team/invites/{inviteId}MessageResponseGET /team/roles/{roleId}TeamRoleData(method in the next PR)POST /team/rolesCreateTeamRoleData {name, description?, permissions}; 201TeamRoleData; 409, 422 (next PR)PUT /team/roles/{roleId}UpdateTeamRoleData(same shape); 200TeamRoleData; 409, 422 (next PR)DELETE /team/roles/{roleId}replacement_role_idquery (DeleteTeamRoleQuery); 200MessageResponse; 409, 422 (next PR)POST /team/roles/{roleId}/duplicateDuplicateTeamRoleData {name, description?}; 201TeamRoleData; 409, 422 (next PR)DELETE /team/members/{userId}MessageResponse; 409RbacConflictException(last PR)GET /team/membersfilter[project_id]query (ListTeamMembersQuery.filter,ListTeamMembersQueryFilter). Returns only members who can access that project; an unknown project returns an empty listTeamRoleDatadescriptionkey, optional (NotRequired) and nullable (str | None)TeamInviteData,TeamInviteDataRole,CreateTeamInviteData,CreateTeamInviteDataProjectAccess,UpdateTeamInviteData,UpdateTeamInviteDataProjectAccess,ListTeamInvitesQuery,ListTeamInvitesQueryPage,ListTeamInvitesResponse,CreateTeamRoleData,UpdateTeamRoleData,DuplicateTeamRoleData,DeleteTeamRoleQuery,ListTeamMembersQueryFilterPOST /team/inviteslists its 404 asModelNotFoundExceptionAll additions are new
TypedDictkeys markedNotRequiredor new types, so existing code keeps type-checking. Python has no compatibility tool in CI; nothing flagged.Usage
Checks
Run locally at the tip of this branch:
python -m ruff check src tests scripts: all checks passedpython -m ruff format --check src tests scripts: 28 files already formattedpython -m mypy: no issues in 35 source filespython scripts/unasync.py --check:src/lettermint/_syncis up to datepython scripts/generate.py --check: the generated files match the pinned spec, and the headers check that CI runs passespython -m pytest tests/ -qon Python 3.10, 3.11, 3.12, 3.13 and 3.14: 279 passed on eachpython -m build,python -m twine check --strict dist/*: passed; the wheel installs in a clean venv and the smoke import passesNot run locally
Nothing.
Suggested release tag
v3.2.0.mainalready has unreleased features sincev3.1.0(messages resend, batch idempotency keys, webhook delivery retry); this stack joins them in the same minor.🤖 Generated with Claude Code