---
title: "Invite a member"
description: "By email, with a role, and optionally with addresses and whole domains that land when they accept."
url: "https://openemail.uk/docs/api/members/add"
area: "API"
category: "Roles & access"
---

# Invite a member

By email, with a role, and optionally with addresses and whole domains that land when they accept.

`POST /members`

## POST /members

By email, with a role, and optionally with addresses and whole domains that land when they accept.

## Example

Needs `members:write`. Returns 202 with the invitation. Nobody is in the workspace until they accept it. `access` defaults to `member` and applies to every id in `addressIds` and `domainIds`.

**curl**

```
curl -X POST "$OE/members" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{
    "email": "sam@acme.com",
    "roleId": "role_2b81de079c1f0a4b7e05d386",
    "domainIds": ["93542ff8-2baa-4f2f-841d-5ceaa074ab0d"],
    "access": "member"
  }'
```

**Response**

```
{
  "object": "invitation",
  "id": "winv_6bb640f5b99e47deb758f1f5",
  "email": "sam@acme.com",
  "role": {
    "id": "role_2b81de079c1f0a4b7e05d386",
    "name": "Support",
    "builtin": null
  },
  "addresses": [],
  "domains": [
    {
      "domainId": "93542ff8-2baa-4f2f-841d-5ceaa074ab0d",
      "domain": "acme.com",
      "access": "member"
    }
  ],
  "expiresAt": "2026-10-02T14:07:31.000Z",
  "delivered": true,
  "deliveryError": null,
  "createdAt": "2026-09-18T14:07:31.000Z"
}
```

> EVERYBODY IS INVITED. Whether or not the address already has an OpenEmail account, the answer is an invitation and a 202, never a member. Nobody is put into a workspace without accepting, and this endpoint is held to the same rule as the app. Somebody already in the workspace is refused with `member_is_owner`, a 422: change what they may do with `PATCH /members/{userId}` and the grant calls instead.

> The invitation carries the role, the addresses and the whole domains you name, and grants exactly those the moment it is accepted. Nothing is granted before that. A whole domain covers every address on it, including ones made after the invitation was sent.

> `POST` rather than `PUT` on a user id, because the caller does not know the user id and may be inviting somebody who has no account yet. They know an email address.

> Asking twice within ten minutes is `invitation_too_soon`, a 409. After that it refreshes the one outstanding invitation, mints a new link and retires the old one, rather than sending a second.

> `delivered: false` means the invitation exists but the email did not leave the mail server. `deliveryError` says what the server said. Nothing reached them, so send it again from the app once the cooldown has passed.

> `invitedBy` is deliberately left null rather than attributed to the workspace owner. The column records which PERSON invited somebody, and an API key is not a person.

> The workspace owner is refused with `member_is_owner`, a 422. They are not a member and cannot be made one.
