# Invites

An invite asks one email address to join a workspace with a role and, optionally, teams. Its emailed link holds a secret token, and accepting it makes the person a member.

## The Invite object

An invitation for one email address to join the workspace.

### Attributes

- `id` (string, required): Unique ID of the invite.
- `workspaceId` (string, required): Unique ID of the workspace.
- `email` (string, required): The lowercased email address the invite was sent to.
- `role` (string, one of: `owner`, `admin`, `member`, `viewer`, required): The role the person gets on accepting: admin, member or viewer.
- `invitedByUserId` (nullable string, required): The user who sent the invite, or null when an account API key sent it.
- `expiresAt` (string, required): When the invite link stops working, as an ISO 8601 timestamp: 7 days after it was sent.
- `acceptedAt` (nullable string, required): When the invite was accepted, as an ISO 8601 timestamp; null until then.
- `revokedAt` (nullable string, required): When the invite was revoked, as an ISO 8601 timestamp; null unless revoked.
- `createdAt` (string, required): When the invite was created, as an ISO 8601 timestamp.
- `updatedAt` (string, required): When the invite last changed, as an ISO 8601 timestamp.
- `teams` (array of objects, required): The teams the person joins on accepting.
  - `inviteId` (string, required): Unique ID of the invite.
  - `teamId` (string, required): Unique ID of the team.
  - `teamName` (string, required): The team's name, such as Web.

### Example

```json
{
  "id": "Iv6WqR3nTb8K",
  "workspaceId": "Wk7Qm2RtXa9L",
  "email": "lee@acme.dev",
  "role": "member",
  "invitedByUserId": "Us3NfP8kLq2V",
  "expiresAt": "2026-10-21T09:05:12.000Z",
  "acceptedAt": null,
  "revokedAt": null,
  "createdAt": "2026-10-14T09:05:12.000Z",
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "teams": [
    {
      "inviteId": "Iv6WqR3nTb8K",
      "teamId": "Tm6PzN3kWq1J",
      "teamName": "Web"
    }
  ]
}
```

## Get an invite by token

`GET /v1/invites/{token}`

Returns the invite a link points to, including expired, revoked and accepted ones, so a person can see which workspace invited them. It needs no API key. Returns `invite: null` when no invite has the token.

### Parameters

- `token` (string, in path, required): The secret token from the invite link, the last part of /invite/{token}.

### Returns

Returns an object with these fields. The status is 200.

- `invite` (nullable object, required): The invite the token belongs to, or null when there is none.
  - `id` (string, required): Unique ID of the invite.
  - `email` (string, required): The lowercased email address the invite was sent to.
  - `role` (string, one of: `owner`, `admin`, `member`, `viewer`, required): The role the person gets on accepting: admin, member or viewer.
  - `workspaceId` (string, required): Unique ID of the workspace.
  - `workspaceName` (string, required): The workspace's display name.
  - `workspaceSlug` (string, required): The workspace's slug, such as acme.
  - `expiresAt` (string, required): When the invite link stops working, as an ISO 8601 timestamp: 7 days after it was sent.
  - `acceptedAt` (nullable string, required): When the invite was accepted, as an ISO 8601 timestamp; null until then.
  - `revokedAt` (nullable string, required): When the invite was revoked, as an ISO 8601 timestamp; null unless revoked.
  - `teams` (array of objects, required): The teams the person joins on accepting.
    - `teamId` (string, required): Unique ID of the team.
    - `teamName` (string, required): The team's name, such as Web.

### Request

```bash
curl https://api.qa.clankership.net/v1/invites/{token}
```

### Response

```json
{
  "invite": {
    "id": "Iv6WqR3nTb8K",
    "email": "lee@acme.dev",
    "role": "member",
    "workspaceId": "Wk7Qm2RtXa9L",
    "workspaceName": "Acme",
    "workspaceSlug": "acme",
    "expiresAt": "2026-10-21T09:05:12.000Z",
    "acceptedAt": null,
    "revokedAt": null,
    "teams": [
      {
        "teamId": "Tm6PzN3kWq1J",
        "teamName": "Web"
      }
    ]
  }
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## List invites

`GET /v1/workspaces/{slug}/invites`

Returns the workspace's open invites: those not accepted or revoked, including expired ones. Any member can list invites.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.

### Returns

Returns an array of [Invite](https://docs.qa.clankership.net/api/invites/object.md) objects. The status is 200.

### Request

```bash
curl https://api.qa.clankership.net/v1/workspaces/{slug}/invites \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
[
  {
    "id": "Iv6WqR3nTb8K",
    "workspaceId": "Wk7Qm2RtXa9L",
    "email": "lee@acme.dev",
    "role": "member",
    "invitedByUserId": "Us3NfP8kLq2V",
    "expiresAt": "2026-10-21T09:05:12.000Z",
    "acceptedAt": null,
    "revokedAt": null,
    "createdAt": "2026-10-14T09:05:12.000Z",
    "updatedAt": "2026-10-14T09:05:12.000Z",
    "teams": [
      {
        "inviteId": "Iv6WqR3nTb8K",
        "teamId": "Tm6PzN3kWq1J",
        "teamName": "Web"
      }
    ]
  }
]
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Create invites

`POST /v1/workspaces/{slug}/invites`

Invites each address with the same role and teams, queues an email with a link that expires in 7 days, and revokes any older open invite to that address. Only owners and admins can invite. Addresses are invited in order and the first failure stops the request, keeping the invites already made.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `emails` (array of strings, required): The email addresses to invite. An address repeated exactly is invited once.
- `role` (string, one of: `owner`, `admin`, `member`, `viewer`, required): The role each person gets: admin, member or viewer. Owner is refused.
- `teamIds` (array of strings, required): IDs of teams each person joins on accepting. Send [] for none.

### Returns

Returns an object with these fields. The status is 200.

- `results` (array of objects, required): One entry per invited address, in request order.
  - `email` (string, required): The address as it was sent in the request.
  - `ok` (boolean, required): Always true: the invite was sent. Always `true`.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/invites \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      "lee@acme.dev"
    ],
    "role": "member",
    "teamIds": [
      "Tm6PzN3kWq1J"
    ]
  }'
```

### Response

```json
{
  "results": [
    {
      "email": "lee@acme.dev",
      "ok": true
    }
  ]
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Revoke an invite

`POST /v1/workspaces/{slug}/invites/{inviteId}/revoke`

Cancels the invite so its link stops working, and records an `invite.revoked` event. Only owners and admins can revoke invites.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `inviteId` (string, in path, required): The invite's ID, such as Iv6WqR3nTb8K.

### Returns

Returns an object with these fields. The status is 200.

- `ok` (boolean, required): Always true: the change was made. Always `true`.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/invites/{inviteId}/revoke \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "ok": true
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Change an invite's role

`PATCH /v1/workspaces/{slug}/invites/{inviteId}`

Sets the role an open invite grants and records an `invite.role_changed` event. Only owners and admins can change it.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `inviteId` (string, in path, required): The invite's ID, such as Iv6WqR3nTb8K.
- `role` (string, one of: `owner`, `admin`, `member`, `viewer`, required): The role the person gets: admin, member or viewer. Owner is refused.

### Returns

Returns an object with these fields. The status is 200.

- `ok` (boolean, required): Always true: the change was made. Always `true`.

### Request

```bash
curl -X PATCH https://api.qa.clankership.net/v1/workspaces/{slug}/invites/{inviteId} \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "role": "viewer"
  }'
```

### Response

```json
{
  "ok": true
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Resend an invite

`POST /v1/workspaces/{slug}/invites/{inviteId}/resend`

Queues a new email with a fresh link that expires in 7 days; the old link stops working. Records an `invite.resent` event. Only owners and admins can resend open invites.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `inviteId` (string, in path, required): The invite's ID, such as Iv6WqR3nTb8K.

### Returns

Returns an object with these fields. The status is 200.

- `ok` (boolean, required): Always true: the change was made. Always `true`.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/workspaces/{slug}/invites/{inviteId}/resend \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "ok": true
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Accept an invite

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

Adds you to the workspace with the invite's role and teams, and marks the invite accepted. Your email address must match the invite's. Fails with NotFound when the invite is expired, revoked or already accepted.

### Parameters

- `token` (string, in path, required): The secret token from the invite link, the last part of /invite/{token}.

### Returns

Returns an object with these fields. The status is 200.

- `ok` (boolean, required): Always true: you joined the workspace. Always `true`.
- `workspaceSlug` (string, required): The slug of the workspace you joined, such as acme.

### Request

```bash
curl -X POST https://api.qa.clankership.net/v1/invites/{token}/accept \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "ok": true,
  "workspaceSlug": "acme"
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.
