# Invite someone

`POST https://evohub.io/api/v1/invitations`

Part of the [Organization API](https://docs-dev.evohub.io/organization.md) reference · operationId `createInvitation`.

Emails an invitation to join the organization, valid for seven days. `role`
is the standing (default `member`); `role_ids` and `team_ids` are what the
person gets on arrival. The key must hold every permission those carry, and
only an administrator invites an administrator.

An invitation names who sent it, so it needs a **personal** key: an
organization key gets **403 `NOT_A_PERSON`**. The answer's `token` is empty
(`""`): the email is the only place the invitation's secret goes.

**Permission (API-key scope):** `identity:user:invite`.

## Authorization

Any one of:

- `bearerAuth` (identity:user:invite)
- `apiKeyHeader` (identity:user:invite)

Where:

- `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token.
- `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`).

## Request body (required)

Content type: `application/json`

Type: `object`

- `email` (string (email), required)
- `role` (string, one of `admin`, `member`, `viewer`, default `member`)
- `role_ids` (array of string)
- `team_ids` (array of string)

## Responses

### 201 — The invitation.

Content type: `application/json`

Type: `object`

- `data` (Invitation, required)
  - `id` (string, required)
  - `org_id` (string, required)
  - `email` (string (email), required)
  - `role` (string, required, one of `admin`, `member`, `viewer`)
  - `role_ids` (array of string | null): Roles held on arrival.
  - `team_ids` (array of string | null): Teams joined on arrival.
  - `token` (string): The secret the invitation email carries. Always empty (`""`) in a response to the organization; only the email carries it.
  - `status` (string, required, one of `pending`, `accepted`, `declined`, `expired`)
  - `invited_by` (string, required)
  - `expires_at` (string (date-time), required): Seven days after it was sent (or last resent).
  - `accepted_at` (string (date-time))
  - `created_at` (string (date-time), required)
- `success` (boolean, required, value `true`)

### 400 — Not JSON (`INVALID_BODY`), no email (`VALIDATION_FAILED`), or `role` is not admin, member or viewer (`VALIDATION_ERROR`).

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): The response's `X-Request-ID`; quote it to support.

### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`).

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): The response's `X-Request-ID`; quote it to support.

### 403 — The key lacks the scope or a permission the invitation grants (`FORBIDDEN`), an administrator is being invited (`ADMIN_ONLY`), or it is an organization key (`NOT_A_PERSON`).

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): The response's `X-Request-ID`; quote it to support.

### 404 — A role or team named does not exist (`NOT_FOUND`).

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): The response's `X-Request-ID`; quote it to support.

### 409 — The address already has a pending invitation (`INVITATION_PENDING`); resend it instead.

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): The response's `X-Request-ID`; quote it to support.

### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds.

Headers:

- `Retry-After` (integer): Seconds to wait.

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): The response's `X-Request-ID`; quote it to support.

### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later.

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): The response's `X-Request-ID`; quote it to support.

## Example request

```bash
curl -X POST 'https://evohub.io/api/v1/invitations' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <TOKEN>' \
  -d '{
  "role": "member",
  "email": "deniz@acme.example",
  "role_ids": [
    "role_8e1f2a3b-4c5d-4e6f-8a9b-0c1d2e3f4a5b"
  ],
  "team_ids": [
    "team_2f9c4b7e-1a3d-4c5e-9f7a-0b2d4e6f8a1c"
  ]
}'
```
