---
title: "Exceptions — Teams for Laravel | Roundly"
description: "Typed exceptions for invites, join requests and members — expired, exhausted, revoked, already resolved — all extending TeamsException."
url: https://roundly-consulting.com/open-source/docs/teams-for-laravel/exceptions
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Teams for Laravel](https://roundly-consulting.com/open-source/docs/teams-for-laravel.md)

# Exceptions

Every exception lives in RoundlyConsulting\\Teams\\Exceptions and extends TeamsException, which extends RuntimeException. Catch the base type for any package error, or a specific one for targeted handling:

| Exception | Thrown by | When |
| --- | --- | --- |
| `InviteExpiredException` | AcceptInviteAction | The invite’s expires\_at has passed. |
| `InviteExhaustedException` | AcceptInviteAction | uses has reached max\_uses — including a single-use invite already consumed. |
| `InviteEmailMismatchException` | AcceptInviteAction | An email-targeted invite gets a different or missing email (compared case-insensitively). |
| `InviteNotFoundException` | `Teams::invites()->accept()` | No invite matches the code, the invite was revoked, or its team was deleted. |
| `InviteNotFoundException` | `for($t)->invites()->resend() / revoke()` | The invite belongs to another team. |
| `JoinRequestNotFoundException` | `for($t)->joinRequests()->approve() / deny()` | The request belongs to another team. |
| `JoinRequestNotPendingException` | `for($t)->joinRequests()->approve() / deny()` | The request was already approved, denied or expired — including by a concurrent responder. |
| `MemberNotFoundException` | `for($t)->members()->changeRole()` | The model is not a member of the team. |
| `TeamsException` | `joinRequests()->open(), members()->add(), transferOwnershipTo()` | The base class. Thrown directly for an invite-only team’s join request, a join request from an active member, and a reached seat limit. |

```php
use RoundlyConsulting\Teams\Exceptions\InviteEmailMismatchException;
use RoundlyConsulting\Teams\Exceptions\InviteExhaustedException;
use RoundlyConsulting\Teams\Exceptions\InviteExpiredException;
use RoundlyConsulting\Teams\Exceptions\InviteNotFoundException;
use RoundlyConsulting\Teams\Exceptions\TeamsException;

try {
    Teams::invites()->accept($code, $user, email: $user->email);
} catch (InviteExpiredException) {
    // the link has expired
} catch (InviteExhaustedException) {
    // no seats left, or a single-use link already consumed
} catch (InviteEmailMismatchException) {
    // the wrong recipient
} catch (InviteNotFoundException) {
    // an unknown code, a revoked invite or a deleted team
} catch (TeamsException $e) {
    // any other teams error — e.g. a seat limit
}
```

The invite checks run expired → exhausted → revoked → email mismatch → deleted team, so the first failing condition is the one you catch.

## Translated messages

The invite, join-request, membership, seat-limit and interval-config messages come from the teams:: translation namespace and follow the app locale — English and Slovak ship:

| Key | English default |
| --- | --- |
| `teams::errors.already_member` | `The given model is already a member of team #:team.` |
| `teams::errors.invite_expired` | `The invite ":code" has expired.` |
| `teams::errors.invite_email_mismatch` | `This invite is addressed to ":email".` |
| `teams::errors.invite_exhausted` | `The invite ":code" has reached its usage limit.` |
| `teams::errors.invite_not_found` | `No invite was found for the code ":code".` |
| `teams::errors.invite_team_missing` | `The team of invite #:id no longer exists.` |
| `teams::errors.invite_unavailable` | `Invite #:id is no longer available.` |
| `teams::errors.invite_not_in_team` | `Invite #:id does not belong to this team.` |
| `teams::errors.join_request_not_pending` | `Join request #:id has already been resolved.` |
| `teams::errors.join_request_not_in_team` | `Join request #:id does not belong to this team.` |
| `teams::errors.member_not_found` | `The given model is not a member of team #:team.` |
| `teams::errors.join_policy_forbids_requests` | `This team is invite-only and does not accept join requests.` |
| `teams::errors.invalid_config_value` | `Configuration value [:key] must be :expected, [:given] given.` |
| `teams::errors.config_expectations.positive_interval` | `a positive interval such as "7 days"` |
| `teams::errors.config_expectations.non_negative_interval` | `a zero or positive interval such as "30 days"` |
| `teams::errors.max_seats_reached` | `This team has reached its seat limit (:count seat).\|This team has reached its seat limit (:count seats).` |

max\_seats\_reached is rendered with trans\_choice(): English uses singular|plural, Slovak {0} …|{1} …|\[2,4\] …|\[5,\*\] …. An override may keep a single line without | — it is rendered as is.

```bash
php artisan vendor:publish --tag="teams-translations"
# edit lang/vendor/teams/en/errors.php or lang/vendor/teams/sk/errors.php, or add other locales
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/teams-for-laravel.md)
