TryMellon
Navigation

Team invitations

Invite, revoke, and resend tenant invitations. Role-based access for admins, developers, and viewers — with a public acceptance flow that needs no prior account.

Team invitations

TryMellon tenants are the container for your team, your applications, and your billing. Inviting a teammate is the primary way new members join a tenant — and unlike most auth providers, invitations are tenant-scoped, role-aware, and acceptable without a pre-existing account.

This page covers the full lifecycle: issue, list, resend, revoke, accept.


Roles

RoleCan
ownerEverything. Manage billing, invite/remove owners, delete the tenant. Assigned at tenant creation — never via invitation.
adminManage applications, members, webhooks, and policies. Invite other admins/developers/viewers.
developerCreate and edit applications. No member or billing access.
viewerRead-only on all tenant resources.

owner is special: it exists to guarantee that a tenant always has at least one person with full control. You cannot grant owner via invitation; ownership is promoted manually through support.


Lifecycle

┌──────────┐    POST /invitations     ┌─────────┐
│  Admin   │─────────────────────────>│ Pending │
└──────────┘                          └─────────┘
                                          │ │ │
          token link (acceptLink)         │ │ │
          delivered by email              │ │ │
                                          │ │ │
                  POST /invitations/accept│ │ │
                          (invitee)  ─────┘ │ │
                                  ACCEPTED  │ │
                                            │ │
           POST …/:id/resend (admin) ───────┘ │
                  new token, same invitation  │

           DELETE …/:id (admin)          ─────┘
                           REVOKED

An invitation has exactly one terminal state: accepted, revoked, or expired. Once terminal it cannot transition further — re-inviting creates a new record.


Quick start

Invite a teammate

POST https://api.trymellonauth.com/v1/tenants/ten_01HXYZ…/invitations
Authorization: Bearer <session_token>
Content-Type: application/json

{
  "email": "[email protected]",
  "role": "developer"
}

Response — 201 Created:

{
  "ok": true,
  "data": {
    "invitation_id": "inv_01HXYZ…",
    "email": "[email protected]",
    "role": "developer",
    "expires_at": "2026-04-24T10:00:00.000Z",
    "accept_link": "https://auth.trymellonauth.com/invite/accept?token=…"
  }
}

TryMellon dispatches the invitation email automatically. accept_link is also returned so you can embed it in your own onboarding emails if you prefer first-party delivery.

Accept an invitation

The invitee doesn’t need a TryMellon account beforehand. When they open the link, your frontend (or the TryMellon-hosted fallback page) posts the token:

POST https://api.trymellonauth.com/v1/invitations/accept
Content-Type: application/json

{
  "token": "itok_01HXYZ…"
}

Response — 200 OK:

{
  "ok": true,
  "data": {
    "user_id": "user_…",
    "tenant_id": "ten_…",
    "role": "developer"
  }
}

If the invitee is new, a user is created and the membership is granted atomically. If they already have an account, the membership is added to their existing user.

List pending invitations

GET https://api.trymellonauth.com/v1/tenants/ten_01HXYZ…/invitations?limit=50&include_expired=false
Authorization: Bearer <session_token>

Response:

{
  "ok": true,
  "data": {
    "invitations": [
      {
        "invitation_id": "inv_…",
        "email": "[email protected]",
        "role": "developer",
        "status": "pending",
        "expires_at": "2026-04-24T10:00:00.000Z",
        "created_at": "2026-04-17T10:00:00.000Z",
        "resend_count": 0,
        "last_resent_at": null
      }
    ],
    "next_cursor": null
  }
}

Resend an invitation

If the first email was lost or ignored, resend issues a fresh token without creating a new invitation record. The audit trail preserves resend_count.

POST https://api.trymellonauth.com/v1/tenants/ten_…/invitations/inv_…/resend
Authorization: Bearer <session_token>

Response:

{
  "ok": true,
  "data": {
    "invitation_id": "inv_…",
    "accept_link": "https://auth.trymellonauth.com/invite/accept?token=…"
  }
}

Revoke an invitation

DELETE https://api.trymellonauth.com/v1/tenants/ten_…/invitations/inv_…
Authorization: Bearer <session_token>

Response:

{
  "ok": true,
  "data": {
    "invitation_id": "inv_…",
    "status": "revoked"
  }
}

Any further use of the previous token fails with INVITATION_REVOKED.


API reference

POST /v1/tenants/:tenant_id/invitations

Create an invitation. Requires an admin (or owner) session in that tenant.

  • Auth: Bearer session token.

  • Body:

    FieldTypeRequiredDescription
    emailstringyesInvitee email.
    role"admin" | "developer" | "viewer"yesRole to grant on acceptance.
  • Returns: { invitation_id, email, role, expires_at, accept_link }.

GET /v1/tenants/:tenant_id/invitations

List invitations. Admin-only.

  • Query:
    • limit (1–100, default 50)
    • cursor (from previous response’s next_cursor)
    • include_expired ("true" | "false", default false)

POST /v1/tenants/:tenant_id/invitations/:invitation_id/resend

Reissue the token. Admin-only. Increments resend_count.

DELETE /v1/tenants/:tenant_id/invitations/:invitation_id

Revoke. Admin-only. Terminal.

POST /v1/invitations/accept

Public — no session required. The token is itself the secret.

  • Body: { token: string }.
  • Returns: { user_id, tenant_id, role }.

Error codes

HTTPCodeCause
400invalid_emailMalformed email.
400invalid_roleRole not in admin | developer | viewer.
403forbiddenCaller is not an admin of the target tenant.
404invitation_not_foundinvitation_id unknown or scoped to another tenant.
409invitation_already_acceptedCannot resend or revoke an accepted invitation.
410invitation_expiredToken past expires_at. Create a new one.
410INVITATION_REVOKEDToken was revoked.
409member_already_existsThe email is already a member of this tenant.

Security model

  • Tenant-scoped. An admin in tenant A cannot list, resend, or revoke invitations for tenant B. The ownership check fails before any use case runs.
  • Token = secret. The accept_link grants access on first use. Treat it like a password: don’t log it, don’t share it via insecure channels.
  • Single-use. An accepted token cannot be replayed.
  • Bounded TTL. Invitations expire after 7 days by default. Configurable per tenant via dashboard.
  • Rate limited. Admin endpoints are subject to the per-tenant rate limit (rate-limit.plugin.ts); brute-forcing accept tokens is IP-rate-limited independently.
  • Audit trail. Every invitation event writes to the tenant audit log with the actor user ID, the target email, and the role.

Webhook events

EventWhen emitted
invitation.issuedA new invitation was created.
invitation.resentAn existing invitation was reissued.
invitation.acceptedThe invitee completed acceptance.
invitation.revokedAn admin revoked the invitation before acceptance.
invitation.expiredInvitation reached expires_at without being accepted (emitted asynchronously).

See Webhook events for the envelope.