Skip to content

feat: add team invites - #40

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

Non-breaking. Adds the team.invites sub-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.py lists those six operations as pending; every other operation is still checked.

  • New team.invites (TeamInvites / AsyncTeamInvites, exported for type hints):
    • list(query=None) and iterate(query=None): pending invites, cursor paginated with page[size] and page[cursor], like team.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 a MessageResponse.
  • Every method uses the team token and takes the usual keyword-only timeout. None takes an idempotency key.
  • A 409 raises ConflictError with the error code (for example stale_resource); a 422 raises ValidationError with the field errors. Empty or .. invite IDs raise LettermintConfigError before any request.

API changes in the regenerated types

Where Change
GET /team/invites New. Pending invites, cursor paginated. ListTeamInvitesQuery, ListTeamInvitesResponse (CursorPage[TeamInviteData])
GET /team/invites/{inviteId} New. Returns TeamInviteData
POST /team/invites New. Body CreateTeamInviteData {email, role_id, project_access {scope, project_ids?}}; 201 TeamInviteData; 409 RbacConflictException, 422
PUT /team/invites/{inviteId} New. Body UpdateTeamInviteData (same shape); 200 TeamInviteData; 409, 422
DELETE /team/invites/{inviteId} New. Revokes a pending invite; 200 MessageResponse
GET /team/roles/{roleId} New. Returns TeamRoleData (method in the next PR)
POST /team/roles New. Body CreateTeamRoleData {name, description?, permissions}; 201 TeamRoleData; 409, 422 (next PR)
PUT /team/roles/{roleId} New. Body UpdateTeamRoleData (same shape); 200 TeamRoleData; 409, 422 (next PR)
DELETE /team/roles/{roleId} New. Optional replacement_role_id query (DeleteTeamRoleQuery); 200 MessageResponse; 409, 422 (next PR)
POST /team/roles/{roleId}/duplicate New. Body DuplicateTeamRoleData {name, description?}; 201 TeamRoleData; 409, 422 (next PR)
DELETE /team/members/{userId} New. Removes a non-Owner member; 200 MessageResponse; 409 RbacConflictException (last PR)
GET /team/members New optional filter[project_id] query (ListTeamMembersQuery.filter, ListTeamMembersQueryFilter). Returns only members who can access that project; an unknown project returns an empty list
TeamRoleData New description key, optional (NotRequired) and nullable (str | None)
New types TeamInviteData, TeamInviteDataRole, CreateTeamInviteData, CreateTeamInviteDataProjectAccess, UpdateTeamInviteData, UpdateTeamInviteDataProjectAccess, ListTeamInvitesQuery, ListTeamInvitesQueryPage, ListTeamInvitesResponse, CreateTeamRoleData, UpdateTeamRoleData, DuplicateTeamRoleData, DeleteTeamRoleQuery, ListTeamMembersQueryFilter
Errors Every new operation answers 401, 403, 404 and 429 like the other team endpoints. POST /team/invites lists its 404 as ModelNotFoundException

All additions are new TypedDict keys marked NotRequired or new types, so existing code keeps type-checking. Python has no compatibility tool in CI; nothing flagged.

Usage

import os

from lettermint import Lettermint

lettermint = Lettermint(team_token=os.environ["LETTERMINT_TEAM_TOKEN"])

invite = lettermint.team.invites.create({
    "email": "jane@acme.com",
    "role_id": "role-id",
    "project_access": {"scope": "selected", "project_ids": ["project-id"]},
})

for pending in lettermint.team.invites.iterate():
    print(pending["email"], pending["role"]["name"])

lettermint.team.invites.revoke(invite["id"])

Checks

Run locally at the tip of this branch:

  • python -m ruff check src tests scripts: all checks passed
  • python -m ruff format --check src tests scripts: 28 files already formatted
  • python -m mypy: no issues in 35 source files
  • python scripts/unasync.py --check: src/lettermint/_sync is up to date
  • python scripts/generate.py --check: the generated files match the pinned spec, and the headers check that CI runs passes
  • python -m pytest tests/ -q on Python 3.10, 3.11, 3.12, 3.13 and 3.14: 279 passed on each
  • python -m build, python -m twine check --strict dist/*: passed; the wheel installs in a clean venv and the smoke import passes

Not run locally

Nothing.

Suggested release tag

v3.2.0. main already has unreleased features since v3.1.0 (messages resend, batch idempotency keys, webhook delivery retry); this stack joins them in the same minor.

🤖 Generated with Claude Code

bjarn and others added 2 commits October 20, 2026 22:37
- 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
bjarn requested a review from a team as a code owner October 20, 2026 20:47
@bjarn
bjarn added this pull request to stack #43 October 20, 2026 20:47
@bjarn
bjarn merged commit fd61abe into main Oct 21, 2026
6 checks passed
@bjarn
bjarn deleted the feat/team-invites branch October 21, 2026 16:10
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