Docs
Search docs

Invite email addresses to join an organization

POST/api/orgs/{org}/invites

createOrgInvite

Takes 1 to 50 addresses and one role, and answers per address. Each new invite is single-use and its accept link is mailed to the address; the token is never returned to the inviter (ADR 0164): only a signed-in account holding the address verified can accept, and the membership it grants is bound to the address. Per-address outcomes: `invited`, `mail_failed` (created but not sent — resend it), `already_invited` (an open invite exists; resend rotates it), `already_has_access` (a membership bound to the address already holds the role or more), `invalid`, and `rate_limited` (the inviting account's mail budget is spent). Entries are trimmed and case-folded, never split; duplicates are dropped. 400 for an empty list or more than 50 entries. The deprecated single `email` field is still accepted in place of `emails`; both or neither is a 422.

Requires authentication using a bearer token or a session cookie — see tokens and scopes.

curl

curl -X POST 'https://neosource.dev/api/orgs/ORG/invites' \
  -H 'Authorization: Bearer $NEOSOURCE_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{}'

fetch

fetch("https://neosource.dev/api/orgs/ORG/invites", {
  method: "POST",
  headers: {
    Authorization: "Bearer $NEOSOURCE_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({}),
});

Path parameters

orgrequired

Organization slug.

string

Request bodyrequired

application/json

CreateOrgInviteRequest

object

An org invite request: exactly one of `emails` and the deprecated `email`.

email

string | nulldeprecated

One address, the pre-ADR-0164-Phase-2 body shape (equivalent to `emails: [email]`). Accepted while a SPA bundle built before the list shape can still be open in a tab, since the web and api images deploy separately. Sending both this and `emails`, or neither, is a 422.

emails

array | null

Email addresses to invite, 1 to 50. Each entry is one address: it is trimmed and case-folded, and the server splits nothing. Duplicates after folding are dropped. Required unless the deprecated `email` is sent instead; sending both is a 422.

items

string

role
one of
  • WorkspaceRole

    string

    Workspace membership role. Ordered low → high (`Member < Admin < Owner`) so that `if actor_role >= WorkspaceRole::Admin` reads naturally.

    "member""admin""owner"

  • null

ttl_seconds

integer | nullint64

Optional override for time-to-live in seconds. Defaults to 7 days. Capped server-side at 30 days.

Responses

200One result per distinct address

application/json

InviteBatchResponse

object

One result per distinct address, in request order.

resultsrequired

array

items

InviteResult

object

What one address of an invite request came to.

emailrequired

string

The address as folded, or the trimmed entry as sent when it is `invalid`.

invite_id

string | null

Set when an invite was created (`invited`, `mail_failed`).

outcomerequired
InviteOutcome

403Forbidden — one of: forbidden, needs_scope

application/json

one of
  • ErrorForbidden
  • NeedsScopeError

    object

    `403` body returned when a classic OAuth token is asked for something its granted scopes do not cover — an explicit private listing without `repo`, or the one-click GitHub mirror without `admin:public_key`. `scope` names the missing grant so the client can say exactly what the token lacks. There is no elevation route to widen it any more (a re-link goes through the GitHub App, whose tokens carry no scopes), so the client's answer is to say so, never to start a handshake.

    errorrequired

    string

    Always `needs_scope` — this body exists to carry the extra fields that kind needs.

    "needs_scope"

    scoperequired

    string

    The provider scope to request via elevation (e.g. `"repo"`).

Standard errors

Bodies documented once for the whole API — see standard errors.

  • 400Bad Request — one of: invalid_input
  • 401Authentication required
  • 404Not Found — one of: not_found
  • 409Conflict — one of: already_exists, conflict
  • 429Rate limited — retry after the `Retry-After` header
  • 500Internal server error
  • 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
  • 504Gateway timeout — the request exceeded the server's handling budget

Schemas

Referenced above. Listed here rather than expanded inline, so the same definition is not repeated at every level.

InviteOutcome

string

Per-address outcome of an invite batch, on the wire (ADR 0164).

"invited""mail_failed""already_invited""already_has_access""invalid""rate_limited"