I'mBoardDocs
Resources

Invites

Invite users to join a board by email, and manage pending invites.

Board invites allow users to invite others to join a board by email. The inviter can include a personal message and pre-assign board positions. Invitees can accept (directly or via an invite code), and inviters can decline on behalf of an invitee or retract a pending invite.

Mostly a legacy surface

Eleven of the twelve endpoints below are legacy — their paths do not carry a /api/v1 prefix and are not reachable at one. Only the invite-status probe (GET /invite/status/:inviteCode) is registered on the versioned public stack and reachable under /api/v1. See Versioning for what "legacy" means in this API.

Endpoints

MethodPathDescription
GET/api/invite/:boardId/invitesList pending invites for a board (legacy)
GET/api/invite/sentList invites the caller has sent, across boards (legacy)
GET/api/invite/all-boardsList pending invites sent, grouped by board (legacy)
POST/api/invite/:boardId/testSend a test invite email to yourself (legacy)
POST/api/invite/:boardIdCreate (or dedupe onto) an invite (legacy)
POST/api/invite/:boardInviteId/send-emailSend/resend the invite email (legacy)
GET/api/invite/:boardId/codeGet or create the board's invite code (legacy)
PUT/api/invite/accept/:boardInviteIdAccept an invite (legacy)
PUT/api/invite/join/:inviteCodeAccept an invite via invite code (legacy)
PUT/api/invite/decline/:boardInviteIdDecline an invite (legacy)
PUT/api/invite/retract/:boardInviteIdRetract a sent invite (legacy)
GET/api/v1/invite/status/:inviteCodeCheck whether an invite code is still pending (public, no auth)

Invite Objects

Two different shapes appear across these endpoints — the list endpoints return a projected view, not the raw stored invite.

Invite Detail (returned by list endpoints)

{
  "inviteId": "60d5ec49f1a2c8b1f8e4e1a1",
  "boardId": "60d5ec49f1a2c8b1f8e4e1b1",
  "boardName": "Acme Corp Board",
  "boardDescription": "Main governance board",
  "inviteeEmail": "[email protected]",
  "inviterId": "507f1f77bcf86cd799439011",
  "inviterEmail": "[email protected]",
  "inviterFirstName": "Alex",
  "inviterLastName": "Admin",
  "inviteMessage": "Looking forward to having you on the board!",
  "status": "pending",
  "createdAt": "2025-06-01T10:00:00.000Z",
  "alsoInvitedBy": []
}
FieldTypeDescription
inviteIdstringOpaque invite ID
boardIdstringThe board the invite belongs to
boardNamestringBoard display name
boardDescriptionstring | nullBoard description
inviteeEmailstringEmail address of the invited person (stored lower-cased)
inviterIdstringUser ID of the person who sent the invite
inviterEmailstringInviter's email
inviterFirstNamestringInviter's first name
inviterLastNamestringInviter's last name
inviteMessagestring | nullOptional personal message (max 500 characters)
statusstringOne of: pending, accepted, declined, retracted
createdAtstringISO-8601 timestamp
alsoInvitedBystring[] | undefinedUser IDs of other admins who re-invited the same email while this invite was pending

Board Positions

Assignable via boardPositions on create; assigned to the invitee on acceptance.

ValueValueValue
executive assistantobserver (default)ceo
board chairboard membercompany secretary
cfocorporate lawyerinvestor
management rolesexternal consultantit administrator
chief of staffother

Duplicate Invites Are Deduped

At most one pending invite exists per (board, email) pair, and email matching is case-insensitive. Inviting an email that already has a pending invite on that board updates the existing invite instead of creating a second one.

On a dedupe:

  • inviter stays the user who created the original invite.
  • The re-inviting user is appended to alsoInvitedBy, and the invite appears in their own sent list.
  • message and boardPositions submitted by a different inviter are ignored, so a re-invite cannot overwrite the original inviter's assignment. A re-invite by the original inviter does update both.

List Pending Invites for a Board (legacy)

GET /api/invite/:boardId/invites

Response

Returns a bare array — not wrapped in { "data": ... }:

[
  /* array of invite detail objects */
]

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token
FORBIDDEN403Token lacks invites:read scope, or user does not have access to this board
RESOURCE_NOT_FOUND404Board does not exist

List Invites Sent (legacy)

GET /api/invite/sent

Returns invites the authenticated user has sent, across every board. JWT-only — API tokens cannot call this endpoint.

Response

Returns a bare array, same shape as above:

[
  /* array of invite detail objects */
]

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token, or called with an API token

List Pending Invites Grouped by Board (legacy)

GET /api/invite/all-boards

Returns pending invites the authenticated user has sent, grouped by board. JWT-only.

Response

Returns a bare object keyed by board ID — not wrapped in { "data": ... }:

{
  "60d5ec49f1a2c8b1f8e4e1b1": [
    /* array of invite detail objects, pending only */
  ]
}

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token, or called with an API token

Send a Test Invite Email (legacy)

POST /api/invite/:boardId/test

Sends a preview of the invite email to the caller's own address, without creating an invite. Rate-limited per inviter.

Response

{
  "message": "Test invite email sent successfully",
  "sentTo": "[email protected]"
}

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token
FORBIDDEN403Caller is not a member of this board
RESOURCE_NOT_FOUND404Board does not exist
RATE_LIMITED429Too many test emails sent recently

Create an Invite (legacy)

POST /api/invite/:boardId

Invites a user to a board by email. Optionally include a personal message and assign board positions. Requires at least collaborator access. Creating an invite does not send an email — use the send-email endpoint below.

Request Body

FieldTypeRequiredDescription
inviteeEmailstringYesEmail address of the person to invite (normalized to lowercase)
messagestringNoPersonal message (max 500 characters, HTML-escaped)
boardPositionsstring[]NoBoard positions to assign on acceptance (defaults to ["observer"])

Response

Returns 200:

{
  "message": "Invite added to access list successfully",
  "inviteId": "60d5ec49f1a2c8b1f8e4e1a1",
  "deduped": false,
  "triggerNudge": false
}
FieldTypeDescription
messagestringHuman-readable result
inviteIdstringID of the pending invite (existing one when deduped is true)
dedupedbooleantrue when the request collapsed onto an invite that already existed
triggerNudgebooleantrue when the response should prompt the inviter to add their name to their profile

Errors

CodeStatusWhen
INVALID_REQUEST_BODY400Missing/invalid fields (e.g. invalid email)
UNAUTHENTICATED401Missing or invalid token
FORBIDDEN403Token lacks invites:write scope, or caller is not at least a collaborator
BILLING_RESTRICTED403Billing not active
RESOURCE_NOT_FOUND404Board does not exist
RATE_LIMITED429Too many invites created recently

Send/Resend the Invite Email (legacy)

POST /api/invite/:boardInviteId/send-email

Sends (or resends) the invite email for a pending invite. JWT-only. Throttled to 1 email per hour per invite and 5 per 24 hours per recipient address.

Response

Success:

{
  "message": "Invite email sent",
  "sentTo": "[email protected]",
  "lastSentAt": "2025-06-01T10:00:00.000Z",
  "throttled": false
}

Throttled (still 200):

{
  "message": "...",
  "sentTo": "[email protected]",
  "lastSentAt": "2025-06-01T09:10:00.000Z",
  "throttled": true,
  "throttleReason": "per-invite"
}

throttleReason is "per-invite" or "global-recipient", naming which limit applied.

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token, or called with an API token
RESOURCE_NOT_FOUND404Invite does not exist

Get or Create the Board's Invite Code (legacy)

GET /api/invite/:boardId/code

Returns the board's shareable invite code, creating one if it doesn't exist yet.

Response

Bare object, not wrapped in { "data": ... }:

{ "inviteCode": "a1b2c3d4-...-uuid" }

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token
FORBIDDEN403Caller is not a member of this board
RESOURCE_NOT_FOUND404Board does not exist

Accept an Invite (legacy)

PUT /api/invite/accept/:boardInviteId

Accepts a board invite by ID. Send no request body — this endpoint rejects any body. The authenticated user's email must match the invite's inviteeEmail (case-insensitive). On success, the user is added to the board with the positions specified in the invite.

Response

Bare object, not the invite/board-invite object:

{ "message": "Successfully accepted board invite", "triggerNudge": false }

triggerNudge is true when the accepting user has no first name set, signaling the client to prompt for one.

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token
FORBIDDEN403Token lacks invites:write scope, or the authenticated user's email does not match the invite
RESOURCE_NOT_FOUND404Invite does not exist

Accept an Invite by Code (legacy)

PUT /api/invite/join/:inviteCode

Accepts a board invite via the board's shareable invite code (distinct from a per-invite inviteToken) rather than a specific invite ID. Matches a pending invite by the caller's email on that board. Send no request body. JWT-only.

Response

{
  "message": "Successfully joined the board",
  "boardId": "60d5ec49f1a2c8b1f8e4e1b1",
  "boardName": "Acme Corp Board"
}

An invalid or expired code returns 404 with { "message": "Invalid or expired invite link", "code": "INVALID_INVITE_CODE" } — a bare body, not the standard error envelope.

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token, or called with an API token
FORBIDDEN403No pending invite exists for the caller's email on this board
RESOURCE_NOT_FOUND404Invite code does not exist

Check an Invite Code's Status (public)

GET /api/v1/invite/status/:inviteCode

An unauthenticated probe for whether an invite is still pending — used by the pre-signup join page to render "this invitation is no longer valid" instead of bouncing to sign-in. Requires both the invite code (path) and its per-invite token (query, i) to be valid UUIDv4 strings; the token is a capability credential, not merely an identifier, and is redacted from server logs.

Query Parameters

ParameterTypeRequiredDescription
istringYesThe invite's token (UUIDv4), from the emailed join link

Response

{ "pending": true }

No-oracle by design

Every failure mode — malformed invite code, malformed or missing i, unknown code, unknown/foreign token, retracted/declined/accepted invite — returns the exact same { "pending": false } with a 200 status. The response never distinguishes why an invite isn't pending, so this endpoint cannot be used to enumerate invites or confirm an email's registration status.

Errors

This endpoint does not return error responses for invalid input (see above). No auth-related errors apply since it is unauthenticated.

Decline an Invite (legacy)

PUT /api/invite/decline/:boardInviteId

Declines a board invite. The authenticated user's email must match the invite's inviteeEmail. Send no request body — this endpoint rejects any body; the decline-vs-retract distinction is set by which route you call, not by a payload field.

Response

{ "message": "Successfully declined board invite" }

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token
FORBIDDEN403Token lacks invites:write scope, or the caller's email does not match the invite
RESOURCE_NOT_FOUND404Invite does not exist

Retract a Sent Invite (legacy)

PUT /api/invite/retract/:boardInviteId

Retracts a pending invite that you previously sent. Requires the caller to be the original inviter or a board admin. Send no request body — this endpoint rejects any body.

Response

Note

The response message says "declined" for both this endpoint and Decline above — a naming quirk in the shared handler, not a bug in your integration.

{ "message": "Successfully declined board invite" }

Errors

CodeStatusWhen
UNAUTHENTICATED401Missing or invalid token
FORBIDDEN403Token lacks invites:write scope, or caller is neither the original inviter nor a board admin
RESOURCE_NOT_FOUND404Invite does not exist

On this page