---
title: "Accept an invitation"
method: POST
path: "/v1/invitations/{token}/accept"
tags: ["invitations"]
---

# Accept an invitation

`POST /v1/invitations/{token}/accept`

## Request

- Base URL: `https://api.patricia.app` (the document declares no server; this is the origin it was published from)
- URL: `https://api.patricia.app/v1/invitations/{token}/accept`
- Auth: one of:
  - HTTP bearer
  - none

Accept a workspace invitation token and receive a session token pair.

The raw token from the invitation email is the credential for the ordinary
case: an invitee with no existing account accepts anonymously and lands
logged in (access + refresh tokens) scoped to the invitation's tenant with
the invited role. An ``Authorization`` header is optional, and needed only
to complete the one case below that a token alone cannot: POST anonymously
first, and send the bearer only if this endpoint asks for it.

Errors:
- **404**: unknown token.
- **400**: expired or revoked invitation.
- **409**: invitation already accepted, the workspace membership could not be
  linked to the account that accepted it, or the workspace has no active owner and
  the invitation would bring back its last owner row with a lower role
  (``errors[0].type`` is ``last_owner``). The second is not retryable by the
  invitee: the acceptance is rolled back and an admin has to sort the membership
  out, most often because that person already belongs to this workspace under
  another address. The third is rolled back too and the invitation stays pending:
  the workspace needs an owner restored, which Patricia support escalates.
- **401** ``INVITATION_SIGN_IN_REQUIRED``: the invited address already has an
  account. Sign in, then POST this endpoint again with the bearer token; the
  invitation is untouched and still usable.
- **403** ``INVITATION_WRONG_ACCOUNT``: a bearer was sent, but it is signed in
  as somebody other than the invited address's owner — most often a forwarded
  link. Sign in as the invited address and retry. The invitation is untouched,
  and the message deliberately does not say who it was for.
- **403** ``FORBIDDEN``: every other refusal on this route — the account needs
  recovery, the account can no longer be used, the invitation went to a secondary
  address rather than the account's main one, the two identity planes disagree
  about who is joining, or the membership did not come out active. The invitation
  stays pending in all of them. They share the generic code on purpose: the
  dashboard shows the message, and there is no action the invitee can take that a
  distinct code would unlock. Deliberately not enumerated in the response
  description — a count there goes stale the first time one is added.

## Path parameters

- `token` string, required: The raw invitation token received via email

## Response `200`

Successful Response

- TokenResponse: Access + refresh token pair. ``expires_in`` is the access-token lifetime (seconds).
  - `access_token` string, required
  - `refresh_token` string, required
  - `token_type` string
  - `expires_in` integer, required
  - `landing_hint` 'pending_invitation' | 'diverted_unknown_workspace': A non-authorization hint explaining WHY a session landed where it did. The dashboard reads it to show the right next step instead of a bare landing. It never grants access: the token's tenant claim (or its absence) is the sole authority. Two values, and they differ in whether the session has a workspace at all: ``PENDING_INVITATION`` lands TENANT-LESS — a returning user with at least one pending invitation and no active available home. It is NOT "only pending memberships": the branch is chosen on ``has_pending`` before the deactivated/unavailable fallback, so a user holding a pending invite alongside deactivated or unavailable ones lands here too. The SSO guard declines to mint an empty duplicate tenant, and the hint points them at accepting the invite / signing in from the connected workspace. ``DIVERTED_UNKNOWN_WORKSPACE`` lands WITH an active workspace — a returning user who signed in from a chat workspace Patricia does not know. The guard diverts them to their existing primary workspace rather than minting a second one, and the hint is what lets the dashboard say so and offer a deliberate way to create a separate workspace instead of failing silently.
  - `decision_key` string, nullable

## Other responses

- `401`: `INVITATION_SIGN_IN_REQUIRED` — the invited address already has an account. Sign in and POST this endpoint again with the bearer token; nothing was consumed.
- `403`: `INVITATION_WRONG_ACCOUNT` — a bearer was sent, but it belongs to someone other than the invited address's owner. Sign in as the invited address and retry; nothing was consumed. Every OTHER 403 on this route carries the generic `FORBIDDEN` code, is not retryable by the invitee, and needs an admin; the message says which case it is. The invitation stays pending in all of them.
- `409`: `CONFLICT` — the invitation was already accepted, the workspace membership could not be linked to the account that accepted it, or the workspace has no active owner and the invitation would bring back its last owner row with a lower role (`errors[0].type` is `last_owner`). The second is not retryable: the acceptance is rolled back and an admin has to sort the membership out, most often because that person already belongs to this workspace under another address. The third is rolled back too and the invitation stays pending: the workspace needs an owner restored, which Patricia support escalates.
- `422`: Validation Error

---

[API](https://skmtc.dev/patricia/apis/patricia-api.md) · [All operations](https://skmtc.dev/patricia/apis/patricia-api/llms.txt) · [OpenAPI document](https://skmtc.dev/patricia/apis/patricia-api/revisions/0dfb44495ea1?raw)
